企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持知识库和私有化部署方案 广告
>[info] 本文作者:http://www.kancloud.cn/@runningday > 文档地址:http://www.kancloud.cn/runningday/fabric **默认的 POST /registrar接口** 官方REST接口当中,有一个`POST /registrar`接口,对应的实现为 ~~~ // fabric/core/rest/rest_api.go func Register() ~~~ 实际上,这个接口的作用是login,而不是注册新用户,有必要自己写一个接口,提供注册新用户的功能。 **REST接口路由** 所有的REST接口,都定义在`fabric/core/rest/rest_api.go`文件中,通过如下方式添加路由: ~~~ // fabric/core/rest/rest_api.go router.Post("/registrar", (*ServerOpenchainREST).Register) ~~~ 因此,可以仿照此,添加一个新的接口: ~~~ // fabric/core/rest/rest_api.go router.Post("/users", (*ServerOpenchainREST).CreateUser) ~~~ 即`POST /users`接口提供注册功能 **RegisterUser核心接口** 由于membersrvc提供了CA相关的服务,查阅官方文档[CA API](http://hyperledger-fabric.readthedocs.io/en/v0.6/API/MemberServicesAPI/),了解到ECAA提供了注册用户的核心接口: ~~~ /fabric/membersrvc/ca/ecaa.go func (ecaa *ECAA) RegisterUser(ctx context.Context, in *pb.RegisterUserReq) (*pb.Token, error) ~~~ **RegisterUserReq结构体介绍** 首先看请求参数`in *pb.RegisterUserReq`: ~~~ // ca.pb.go type RegisterUserReq struct { Id *Identity Role Role Attributes []*Attribute Affiliation string Registrar *Registrar Sig *Signature } ~~~ * Id:即新用户的enrollID,由注册时用户提供 * Role:新用户的类型,分为几个限定类型,按位掩码规则 * Attributes:额外属性 * Affiliation:新用户所属机构,也是几个限定值,必须是`AffiliationGroups`数据库表中已存在的值 * Registrar:本身也是一个结构体: ~~~ // ca.pb.go type Registrar struct { Id *Identity Roles []string DelegateRoles []string } ~~~ 它定义了为新用户A提供注册代理的注册者B,这个有必要说一下,在fabric中,注册用户不是用户A向系统发出单纯注册请求即可,而是在请求中需要包含注册者B信息,有点类似于网站的强制邀请注册。注册者B必须有注册该类型用户A的权限,否则系统会拒绝这次请求,并提示: ~~~ member <registrar> is not a registrar ~~~ 其中Registrar.Roles定义了新用户A,以后可注册哪几个类型新用户;而Registrar.DelegateRoles则存储了以后可以升级为Registrar.Roles的值,换句话说,就是该用户A注册的新用户C,以后可注册哪几个类型新用户。例如: ~~~ userReq := RegisterUserReq{ Id: Identity{Id: "Jim"}, Role: Role(1), Affiliation: "institution_a", Registrar: Registrar{ Id: Identity{Id: "admin"}, Roles: ["client", "peer", "validator"], DelegateRoles: ["client"], }, Sig: nil, } ~~~ 这个是注册新用户“Jim”的请求,enrollID(即用户名)为“Jim”;用户类型为1,即“client”;机构隶属为“institution_a”;Attributes留空;注册者是"admin",它是membersrvc默认的管理员账户,拥有注册各种类型用户的权限,其定义在membersrvc.yaml文件中, >[warning] 需要说明一点的就是:admin虽然在配置文件中已定义了很高的权限,但是在注册新用户之前,首先需要把admin用户enroll到系统中,为的是将admin证书写入到数据库Certificates表中,这个操作一次即可。 继续余下的字段,Jim若注册成功,他将可以作为["client", "peer", "validator"]这3种角色的注册者;但是由Jim注册的新成员,最多只能作为["client"]的注册者。 * Sig:上例中Sig留空,这个字段存储了userReq请求的签名,一个请求在发送之前,必须要通过注册者私钥进行签名,如果留空则后续处理会报空指针错误;如果签名错误,系统也会提示: ~~~ Signature verification failed. ~~~ **给请求签名** 注册前的最后一步就是给注册请求签名了,即给userReq.Sig赋予正确的值,签名的私钥用的是注册者的enrollmentKey(或者叫做privateKey),可以通过读取文件`viper.GetString("peer.fileSystemPath") + "/crypto/client/admin/ks/raw/enrollment.key"`得到raw数据,然后使用`primitives.PEMtoPrivateKey(raw, adminEnrollPwd)`解密得到enrollmentKey: ~~~ var path = viper.GetString("peer.fileSystemPath") + "/crypto/client/admin/ks/raw/enrollment.key" raw, err := ioutil.ReadFile(path) if err != nil { fmt.Println("failed loading registrar private key: " + err.Error()) return nil, err } privateKey, err := primitives.PEMtoPrivateKey(raw, []byte("Xurw3yU9zI0l")) if err != nil { fmt.Println("failed parsing private key: " + err.Error()) return nil, err } var registrarPrivateKey = privateKey.(*ecdsa.PrivateKey) ~~~ 但这并不是一个好的方法,最好能找到已经封装好的接口,通过查看`fabric/core/crypto/node_impl.go`源码,可以看到在`nodeImpl`结构体中,包含privateKey字段: ~~~ // fabric/core/crypto/node_impl.go type nodeImpl struct { enrollPrivKey *ecdsa.PrivateKey } ~~~ 但这个字段并没有暴露出来,所以还需要找找别的方式。 后来在`fabric/core/crypto/crypto.go`文件中发现: ~~~ // fabric/core/crypto/crypto.go type CertificateHandler interface { // Sign signs msg using the signing key corresponding to the certificate Sign(msg []byte) ([]byte, error) } ~~~ 这里介绍说使用和证书对应的签名秘钥,对数据进行签名,因为只有私钥才会用于签名,而公钥常常用来加密,所以这个应该正是我所需要的,再看Sign()的实现: ~~~ // fabric/core/crypto/client_ecert_handler.go // Sign signs msg using the signing key corresponding to this ECert func (handler *eCertHandlerImpl) Sign(msg []byte) ([]byte, error) { return handler.client.signWithEnrollmentKey(msg) } // fabric/core/crypto/node_sign.go func (node *nodeImpl) signWithEnrollmentKey(msg []byte) ([]byte, error) { return primitives.ECDSASign(node.enrollPrivKey, msg) } ~~~ 这里赫然写着node.enrollPrivKey,正是私钥。所以使用CertificateHandler接口中的Sign()方法,是一个更好的途径。 余下的流程就简单了: ~~~ // sign the registration request with Registrar's private key // 获取"admin"注册者客户端实例 adminClient, err := crypto.InitClient("admin", nil) // 获取客户端的CertificateHandler接口 handler, err := adminClient.GetEnrollmentCertificateHandler() // 将请求数据序列化为[]byte raw, _ := proto.Marshal(registerUserReq) // 使用admin的私钥对请求签名 result, err := handler.Sign(raw) // 签名结果是序列化处理的[]byte,需要将其反序列化,以构建RegisterUserReq.Sig结构体 var ecdsaSignature = new(primitives.ECDSASignature) asn1.Unmarshal(result, ecdsaSignature) R, _ := ecdsaSignature.R.MarshalText() S, _ := ecdsaSignature.S.MarshalText() registerUserReq.Sig = &pbb.Signature{Type: pbb.CryptoType_ECDSA, R: R, S: S} ~~~ **发送注册请求** 注册者指定完成并已经enroll进CA系统中,请求已被正确签名,这时就可以发送注册请求了: ~~~ var address = "localhost:7054" conn, err := grpc.Dial(address, grpc.WithInsecure()) // 连接address中指定的grpc端口 defer conn.Close() ecaa := protos.NewECAAClient(conn) token, err := ecaa.RegisterUser(context.Background(), registerUserReq) ~~~ **注册成功返回值** 一旦注册成功,token即可接收到方法返回值,token是个结构体: ~~~ // ca.pb.go type Token struct { Tok []byte `protobuf:"bytes,1,opt,name=tok,proto3" json:"tok,omitempty"` } ~~~ token.Tok即为新用户的enrollPwd。