go-grpc实践指南-03认证
认证
grpc默认内置了两种认证方式
- SSL/TLS认证方式
- 基于Token的认证方式
同时,gRPC提供了接口用于扩展自定义认证方式
TLS认证示例-客户端、服务端双向认证
Token认证示例
再进一步,继续扩展hello-tls项目,实现TLS + Token认证机制
目录结构:
示例代码
先修改客户端实现:client/main.go
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"io/ioutil"
"log"
"my_grpc/proto/hello"
)
const (
OpenTLS = true
)
// 自定义认证
type customCredential struct {}
// GetRequestMetadata 实现自定义认证接口
func (c *customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appid": "101010",
"appkey": "i am key",
}, nil
}
// RequireTransportSecurity 自定义认证是否开启TLS
func (c customCredential) RequireTransportSecurity() bool {
return OpenTLS
}
func main() {
var opts []grpc.DialOption
cert, err := tls.LoadX509KeyPair("keys/client/client.pem", "keys/client/client.key")
if err != nil {
log.Fatalln("tls.LoadX509 err:", err)
}
certPool := x509.NewCertPool()
certBytes, err := ioutil.ReadFile("keys/ca/ca.crt")
if err != nil {
log.Fatalln("读取ca证书失败:", err)
}
certPool.AppendCertsFromPEM(certBytes)
tcreds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},// 放入客户端证书
ServerName: "localhost", //证书里面的 commonName
RootCAs: certPool, // 证书池
})
creds := grpc.WithTransportCredentials(tcreds)
opts = append(opts, creds)
// 使用自定义认证
opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
clientConn, err := grpc.Dial(":9000", opts...)
if err != nil {
log.Fatalln("grpc.Dial err:", err)
}
helloClient := hello.NewHelloClient(clientConn)
reply, err := helloClient.SayHello(context.Background(), &hello.HelloRequest{Name: "gRPC"})
if err != nil {
log.Fatalln("helloClient.SayHello err:", err)
}
fmt.Println(reply)
}
这里我们定义了一个customCredential结构,并实现了两个方法GetRequestMetadata和RequireTransportSecurity。这是gRPC提供的自定义认证方式,每次RPC调用都会传输认证信息。customCredential其实是实现了grpc/credential包内的PerRPCCredentials接口。每次调用,token信息会通过请求的metadata传输到服务端。下面具体看一下服务端如何获取metadata中的信息。
修改server/main.go中的SayHello方法:
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"io/ioutil"
"log"
"my_grpc/proto/hello"
"net"
)
type HelloService struct{
hello.UnimplementedHelloServer
}
func (t *HelloService) SayHello(ctx context.Context, in *hello.HelloRequest) (*hello.HelloResponse, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, grpc.Errorf(codes.Unauthenticated, "无Token认证信息")
}
var (
appid string
appkey string
)
if val, ok := md["appid"]; ok {
appid = val[0]
}
if val, ok := md["appkey"]; ok {
appkey = val[0]
}
if appid != "101010" || appkey != "i am key" {
return nil, grpc.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey)
}
resp := new(hello.HelloResponse)
resp.Message = fmt.Sprintf("Hello %s. Token info: appid=%s,appkey=%s", in.Name, appid, appkey)
return resp, nil
}
func main() {
// 使用tls进行加载key pair对
certifacate, err := tls.LoadX509KeyPair("keys/server/server.pem", "keys/server/server.key")
if err != nil {
log.Fatalln("tls加载X509失败,err:",err)
}
// 创建证书池
certPool := x509.NewCertPool()
// 向证书池中加入证书
certBytes, err := ioutil.ReadFile("keys/ca/ca.crt")
if err != nil {
log.Fatalln("读取ca.crt证书失败,err:", err)
}
// 加载证书从pem文件里面
certPool.AppendCertsFromPEM(certBytes)
// 创建credentials对象
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{certifacate}, // 服务端证书
ClientAuth: tls.RequireAndVerifyClientCert, // 需要并且验证客户端证书
ClientCAs: certPool, // 客户端证书池
})
listen, _ := net.Listen("tcp", ":9000")
gServer := grpc.NewServer(grpc.Creds(creds))
hello.RegisterHelloServer(gServer, &HelloService{})
fmt.Println("Listen ont 9000 with TLS + Token")
_ = gServer.Serve(listen)
}
服务端可以从context中获取每次请求的metadata,从中读取客户端发送的token信息并验证有效性。
运行:
$ go run main.go
Listen on 50052 with TLS + Token
运行客户端程序 client/main.go:
$ go run main.go
// 认证成功结果
Hello gRPC
Token info: appid=101010,appkey=i am key
// 修改key验证认证失败结果:
rpc error: code = 16 desc = Token认证信息无效: appID=101010, appKey=i am not key
至此我们完成了grpc的TLS客户端与服务器的双向认证以及自定义的Token认证。(双认证同时使用)