Hyperledger Fabric——balance transfer(二)注册用户

详细分析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 一步完成)
posted @ 2018-03-28 19:31  zhayujie  阅读(324)  评论(0编辑  收藏  举报