详细分析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. Libra教程之:运行自定义move modules

    文章目录 简介 创建Move modules 启动本地网络 创建账号并送测试币 编译Move Module 发布编译好的Module 创建交易脚本 编译编译脚本 执行脚本 简介 因为Libra和Mov ...

  2. 《Arduino实战》——3.4 小结

    本节书摘来异步社区<Arduino实战>一书中的第3章,第3.4节,作者:[美]Martin Evans ,Joshua Noble ,Jordan Hochenbaum,更多章节内容可以 ...

  3. Next.js 7发布,构建速度提升40%

    Next.js团队发布了其开源React框架的7版本.该版本的Next.js主要是改善整体的开发体验,包括启动速度提升57%.开发时的构建速度提升40%.改进错误报告和WebAssembly支持. \ ...

  4. 如何将Superset嵌入后台系统之实践

    1. 前言 此次实践过程全属个人学习,我选择了在window下安装Superset,并进行嵌入后台系统实践.对此进行实践过程总结,实践成果分享给大家,供大家参考,如果你有更好的想法,欢迎留言交流. 2 ...

  5. C++获取当前系统时间并格式化输出

    C++中与系统时间相关的函数定义在头文件中. 一.time(time_t * )函数 函数定义如下: time_t time (time_t* timer); 获取系统当前日历时间 UTC 1970- ...

  6. 概率dp部分题目

    记录一些比较水不值得单独写一篇blog的概率dp题目 bzoj3036 绿豆蛙的归宿 Description 随着新版百度空间的下线,Blog宠物绿豆蛙完成了它的使命,去寻找它新的归宿. 给出一个有向 ...

  7. Jmeter 数据库测试参数化

    1.JDBC Request 参数化 方法一.Jmeter 参数化,在 sql query 中使用变量 Jmeter 参数化,使用 csv 参数化 sql query 中使用 ${变量名} 引用 方法 ...

  8. awk调用外部程序

    程序的功能很简单: 调用外部解密程序decoder,将文件第二列字段解密,然后写入新文件中. BEGIN { OFS = "\t" } { outputFileName = &qu ...

  9. G - Island Transport 网络流

    题目: In the vast waters far far away, there are many islands. People are living on the islands, and a ...

  10. 一些常见的pro文件配置

    UI_DIR = ./ui #ui文件目录 TARGET = Test #最终生成目标名 DESTDIR = $$PWD/../test #目标生成目录,$$PWD表示当前目录下 DLLDESTDIR ...