Hyperledger Fabric——balance transfer(二)注册用户
详细分析blance transfer示例的用户注册(register)与登录(enroll)功能。
源码分析
1.首先分析项目根目录的app.js
文件中关于用户注册和登录的路由函数。注意这里的token
很重要,在之后的请求中,只要在请求头中附上token
,js的路由函数就能直接获取其中的参数:req.username
,req.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
对象时,而是一个含有username
和password
属性的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 一步完成)