详细分析blance transfer示例的用户注册(register)与登录(enroll)功能。

源码分析

1.首先分析项目根目录的app.js文件中关于用户注册和登录的路由函数。注意这里的token很重要,在之后的请求中,只要在请求头中附上token,js的路由函数就能直接获取其中的参数:req.usernamereq.orgName

// 注册和登录用户
app.post('/users', async function(req, res) {
// 获取并检验参数
var username = req.body.username;
var orgName = req.body.orgName;
if (!username) {
res.json(getErrorMessage('\'username\''));
return;
}
if (!orgName) {
res.json(getErrorMessage('\'orgName\''));
return;
}
// 生成token,包含信息username, orgName
var token = jwt.sign({
exp: Math.floor(Date.now() / 1000) + parseInt(hfc.getConfigSetting('jwt_expiretime')),
username: username,
orgName: orgName
}, app.get('secret')); // 注册成功则返回响应信息,并附上token
let response = await helper.getRegisteredUser(username, orgName, true);
if (response && typeof response !== 'string') {
response.token = token;
res.json(response); // 注册失败则返回错误信息
} else {
res.json({success: false, message: response});
} });

2.继续进入helper.js,找到函数getRegisteredUser()分析其具体实现:内部

需要注意的是用户的登陆(enroll)过程是在SDK中的setUserContext()函数内部实现的(登录admin也是),当传入的不是User对象时,而是一个含有usernamepassword属性的object时,会调用_setUserFromConfig()方法,在内部进行登录操作,返回一个User对象。随后setUserContext()将该用户对象存储到到本地(persistence)。

由于SDK在创建该对象的时候没有设置_enrollmentSecret属性,所以在注册登录的返回信息和本地存储的键值对中都没有sectect值,所以可以对源码进行一些修改(不太优雅),或者可以直接修改SDK代码.

var getRegisteredUser = async function(username, userOrg, isJson) {
try {
// 获得一个客户端实例
var client = await getClientForOrg(userOrg); // 通过SDK中的getUserContext()方法获取User对象(包含证书和私钥等信息)
// 本质上是通过KeyValueStore接口从本地存储加载User数据
var user = await client.getUserContext(username, true);
// 用户存在且已经登录
if (user && user.isEnrolled()) {
logger.info('Successfully loaded member from persistence');
}
// 用户未登录,需要使用admin来进行注册
else {
// 根据config.json配置文件中的信息,获得的admins结构如下:
// admins = [{username:"admin", secret:"adminpw"}]
var admins = hfc.getConfigSetting('admins');
let adminUserObj = await client.setUserContext({username: admins[0].username, password: admins[0].secret});
// 生成CA服务实现
let caClient = client.getCertificateAuthority();
// 通过CA服务和admin来注册该user,获得密码secret
let secret = await caClient.register({
enrollmentID: username,
affiliation: userOrg.toLowerCase() + '.department1'
}, adminUserObj);
// enroll成功,将用户上下文信息(证书,私钥)以及密钥对持久化地很存入本地存储中
user = await client.setUserContext({username:username, password:secret});
// 改动:添加属性后重新存入本地存储,以保证获取secret值
user._enrollmentSecret = secret;
await client.setUserContext(user);
}
if(user && user.isEnrolled) {
// 返回包含登录信息的json对象
if (isJson && isJson === true) {
var response = {
success: true,
secret: user._enrollmentSecret,
message: username + ' enrolled Successfully',
};
return response;
}
} else {
throw new Error('User was not enrolled ');
}
} catch(error) {
logger.error('Failed to get registered user: %s with error: %s', username, error.toString());
return 'failed '+error.toString();
}
};

3.再来看生成客户端的getClientForOrg()函数:

async function getClientForOrg (userorg, username) {
// 通过加载配置文件来创建客户端实例 // 根据config.js文件中的配置文件路径设置可知:
// 此步是加载 ./artifacts/network-config.yaml 网络配置文件
// 文件中包含 需要进行交互的目标 blockchain 网络的信息
let config = '-connection-profile-path';
let client = hfc.loadFromConfig(hfc.getConfigSetting('network'+config)); // 此步是加载 ./artifacts/userorg.yaml 其中userorg为org1/org2
// 文件中包含用户状态存储路径(用户键值对)以及公私钥的存储路径(一系列密钥对)
client.loadFromConfig(hfc.getConfigSetting(userorg+config)); // 初始化创建状态存储和加密(密钥)存储
await client.initCredentialStores(); // 如果参数中含有username,则在持久存储中查找该用户,如果查到则说明该用户已经注册并登录
// 并将该User对象分配给刚刚生成的clietn实例
if(username) {
let user = await client.getUserContext(username, true);
if(!user) {
throw new Error(util.format('User was not found :', username));
} else {
logger.debug('User %s was found to be registered and enrolled', username);
}
} return client;
}

测试

  • 运行网络

    ./runApp.sh
  • Org1注册并登录用户Jim:

    ORG1_TOKEN=$(curl -s -X POST \
    http://localhost:4000/users \
    -H "content-type: application/x-www-form-urlencoded" \
    -d "username=Jim&orgName=Org1")

    结果:

    {
    "success": true,
    "secret": "ZVGfOMkmQmBH",
    "message": "Jim enrolled Successfully",
    "token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjMyODE0OTQsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Ik9yZzEiLCJpYXQiOjE1MjMyNDU0OTR9.61-rp1Nye2Rg8e-91WCQwrTAkGvSJ0Bw_Y0OHQ-hDcg"
    }

    用户的状态信息存储在./fabric-client-kv-org1 中,其中包含用户私钥的ski标识符signingIdentity(16进制bytes格式)和证书certificate(pem格式)。用户的公私钥(pem格式)存储在/tmp/fabric-client-kvs_peerOrg1中,文件名为私钥的ski。

具体调用

regiseter:

CA客户端向CA server节点,发送注册请求,参数为enrollmentID:username 和 affiliation: org1.department1,其中affiliation 在 CA节点的yaml文件中指定,可修改。 注册返回secret以登录。

enroll:

  • setUserContext({username:username, password:secret})
  • _setUserFromConfig(),CA客户端调用enroll(),发送参数enrollmentID:username, enrollmentSecret:password,登录返回CA节点生成的私钥和证书
  • createUser() 发送参数username, mspid, cryptoContent(私钥,证书)

    构建User对象 :_signingIdentity, _mspId, _cryptoSuite, _identity, _enrollmentSecret(目前版本属性未赋值)

    其中通过 CryptoSuit.importKey(private_key) 将私钥存于本地,文件名为 ski + ‘_priv’

    通过setEnrollment() ——> importKey(certificate) 通过证书生成公钥 并存入本地,文件名为ski+’_pub’
  • 最后再次调用 setUserContext(User object) ——> saveUserToStateStore() 将Use对象存本地,文件名为username, 内容为User对象的序列化(toString())

总结

  • 生成jwt,该token值需要在之后的请求中使用
  • 通过加载配置文件生成客户端实例(设置了目标blockchain网络信息 及 用户状态和公私钥的本地存储路径)
  • 判断(getUserContext),如果用户未登录,则从配置文件获取信息(secret)登录admin
  • 通过CA服务,admin作为注册员,对用户进行注册
  • 登录用户并将用户状态信息和密钥对存入本地(setUserContext 一步完成)

Hyperledger Fabric——balance transfer(二)注册用户的更多相关文章

  1. Hyperledger Fabric——balance transfer(一)启动示例

    Blacne transfer是Hyperledger fabric Node SDK的一个示例应用,主要使用了SDK中fabric-client 和 fabric-ca-client 模块中的API ...

  2. Hyperledger Fabric——balance transfer(六)查询

    balance transfer 提供了很多查询接口,包括链码查询,根据区块号查询区块数据,根据交易ID查询交易信息,查询链上的区块数,查询已安装或已实例化的链码,查询通道. 源码解析 1.调用链码查 ...

  3. Hyperledger Fabric——balance transfer(四)安装和实例化chaincode

    详细解析blance transfer示例的安装(install)和实例化(Instantiate)链码(chaincode)的过程.安装chaincode会根据本地的链码文件生成chaincode镜 ...

  4. Hyperledger Fabric——balance transfer(五)执行交易

    链码安装和实例化之后就可以调用chaincode执行交易,下面分析简单的账户转账操作是如何完成的. 源码分析 1.首先看app.js的路由函数 app.post('/channels/:channel ...

  5. Hyperledger Fabric——balance transfer(三)创建和加入Channel

    详细解析blance transfer示例的创建通道(Channel)和加入节点到通道的过程. 创建Channel 1.首先看app.js的路由函数 var createChannel = requi ...

  6. Hyperledger Fabric链码之二

    上篇文章中我们介绍了链码的概念,本文中我们将介绍Fabric下链码的编写和测试.我们会通过一个简单例子的方式来阐述链码API的使用. 链码API     每一个链码程序都必须实现一个接口Chainco ...

  7. Hyperledger Fabric开发(二):创建网络

    运行fabric-samples项目中的一个例子:first-network,创建第一个网络(Building Your First Network). 该网络共有4个peer节点,划分为2个组织(o ...

  8. Hyperledger Fabric 实战(十二): Fabric 源码本地调试

    借助开发网络调试 fabric 源码本地调试 准备工作 IDE Goland Go 1.9.7 fabric-samples 模块 chaincode-docker-devmode fabric 源码 ...

  9. Hyperledger Fabric链码之三

    在<Hyperledger Fabric链码之一>和<Hyperledger Fabric链码之二>中我们介绍了链码的定义,并通过dev网络测试了测试了自己编写的链码程序. 本 ...

随机推荐

  1. 一款被大厂选用的 Hexo 博客主题

    首先这是一篇自吹自擂的文章,主题是由多位非前端程序员共同开发,目前经过一年半的迭代已经到达 v1.8.0 版本,并且获得大量认可,甚至某大厂员工已经选用作为内部博客,因此我决定写这篇文章向更多人安利它 ...

  2. Java高效开发IntelliJ IDEA 2019.1 新特性

    1. 重构类.文件.符号,Action 搜索 IntelliJ IDEA(以下简称 IDEA) 中的搜索可以分为以下几类 类搜索,比如 Java,Groovy,Scala 等类文件 文件搜索,类文件之 ...

  3. 【蜕变之路】第20天 UUID和时间戳的生成 (2019年3月10日)

    Hello,大家好!我是程序员阿飞!今天主要学习的内容是:字符串UUID的随机生成和时间戳的随机生成.好了,直接进入正题. 1.UUID的随机生成 /*          * uuid的随机生成方式 ...

  4. 已有项目接入git远程仓库

    1.项目根目录初始化git仓库 git init 2.将本地项目与远程仓库关联(首先得在远程创建一个代码仓库) git remote add origin 远程仓库地址 诺,仓库地址就是这个玩意 3. ...

  5. .NET Core+WebApi+EF访问数据新增用户数据

    新建一个.NET Core项目,我使用的IDE是VS2019 依次创建三个Core类库:第一个命名api.Model,第二个api.Common,第三个api.Bo 解释一下这个三类库的作用: 第一个 ...

  6. keep-alive的深入理解与使用(配合router-view缓存整个路由页面)

    原文链接: 点我 在搭建 vue 项目时,有某些组件没必要多次渲染,所以需要将组件在内存中进行‘持久化’,此时 <keep-alive> 便可以派上用场了. <keep-alive& ...

  7. 学数据库你竟然不用用JAVA写代码,可惜你遇到了我! JAVA连接数据库(JDBC)的安装使用教程

    Step 1 你得有Eclipse 没有出门右拐,我教不了你. Step 2 你得有Mysql MySQL的详细安装过程,我在另一篇博客中给出.戳我 Step 3 安装JDBC 可以去官网下,如果用的 ...

  8. POJ - 2387 Til the Cows Come Home (最短路入门)

    Bessie is out in the field and wants to get back to the barn to get as much sleep as possible before ...

  9. Jmeter 后置处理器

    1.JSON Extractor Json extractor 后置处理器用在返回格式为 Json 的 HTTP 请求中,用来获取返回的 Json 中的某个值.并保存成变量供后面的请求进行调用或断言等 ...

  10. Python(Pyautogui 模块)

    1.安装 pyautogui 模块 pip install pyautogui 2.pyautogui 模块相关操作 鼠标操作 # 获取屏幕宽和高 w,h = pyautogui.size() # 在 ...