认证 gRPC默认内置了两种认证方式:
同时,gRPC提供了接口用于扩展自定义认证方式
TLS认证示例 这里直接扩展hello项目,实现TLS认证机制
首先需要准备证书,在hello目录新建keys目录用于存放证书文件。
证书制作 制作私钥 (.key) 1 2 3 4 5 # Key considerations for algorithm "RSA" ≥ 2048-bit $ openssl genrsa -out server.key 2048 # Key considerations for algorithm "ECDSA" ≥ secp384r1 # List ECDSA the supported curves (openssl ecparam -list_curves) $ openssl ecparam -genkey -name secp384r1 -out server.key
自签名公钥(x509) (PEM-encodings .pem
|.crt
) 1 $ openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
自定义信息 1 2 3 4 5 6 7 Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:XxXx Locality Name (eg, city) []:XxXx Organization Name (eg, company) [Internet Widgits Pty Ltd]:XX Co. Ltd Organizational Unit Name (eg, section) []:Dev Common Name (e.g. server FQDN or YOUR name) []:server name Email Address []:xxx@xxx.com
目录结构 1 2 3 4 5 6 7 8 9 10 11 12 |—— hello-tls/ |—— client/ |—— main.go // 客户端 |—— server/ |—— main.go // 服务端 |—— keys/ // 证书目录 |—— server.key |—— server.pem |—— proto/ |—— hello/ |—— hello.proto // proto描述文件 |—— hello.pb.go // proto编译后文件
示例代码 proto/helloworld.proto
及proto/hello.pb.go
文件不需要改动
修改服务端代码:server/main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport ( "fmt" "net" pb "hellogrpc/proto/hello" "context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "log" ) const ( Address = "127.0.0.1:50052" ) type helloService struct {}var HelloService = helloService{}func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error ) { resp := new (pb.HelloResponse) resp.Message = fmt.Sprintf("Hello %s." , in.Name) return resp, nil } func main () { listen, err := net.Listen("tcp" , Address) if err != nil { log.Fatalf("Failed to listen: %v" , err) } creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem" , "../../keys/server.key" ) if err != nil { log.Fatalf("Failed to generate credentials %v" , err) } s := grpc.NewServer(grpc.Creds(creds)) pb.RegisterHelloServer(s, HelloService) log.Println("Listen on " + Address + " with TLS" ) s.Serve(listen) }
运行:
1 2 $ go run main.go Listen on 127.0.0.1:50052 with TLS
服务端在实例化grpc Server时,可配置多种选项,TLS认证是其中之一
客户端添加TLS认证:client/main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport ( pb "hellogrpc/proto/hello" "context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "log" ) const ( Address = "127.0.0.1:50052" ) func main () { creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem" , "yyy" ) if err != nil { log.Fatalf("Failed to create TLS credentials %v" , err) } conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds)) if err != nil { log.Fatalln(err) } defer conn.Close() c := pb.NewHelloClient(conn) req := &pb.HelloRequest{Name: "gRPC" } res, err := c.SayHello(context.Background(), req) if err != nil { log.Fatalln(err) } log.Println(res.Message) }
运行:
1 2 $ go run main.go Hello gRPC
客户端添加TLS认证的方式和服务端类似,在创建连接Dial
时,同样可以配置多种选项,后面的示例中会看到更多的选项。
Token认证示例 再进一步,继续扩展hello-tls项目,实现TLS + Token认证机制
目录结构 1 2 3 4 5 6 7 8 9 10 11 12 |—— hello_token/ |—— client/ |—— main.go // 客户端 |—— server/ |—— main.go // 服务端 |—— keys/ // 证书目录 |—— server.key |—— server.pem |—— proto/ |—— hello/ |—— hello.proto // proto描述文件 |—— hello.pb.go // proto编译后文件
示例代码 先修改客户端实现:client/main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package mainimport ( pb "hellogrpc/proto/hello" "context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "log" ) const ( Address = "127.0.0.1:50052" OpenTLS = true ) type customCredential struct {}func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string ) (map [string ]string , error ) { return map [string ]string { "appid" : "101010" , "appkey" : "i am key" , }, nil } func (c customCredential) RequireTransportSecurity() bool { return OpenTLS } func main () { var err error var opts []grpc.DialOption if OpenTLS { creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem" , "yyy" ) if err != nil { log.Fatalf("Failed to create TLS credentials %v" , err) } opts = append (opts, grpc.WithTransportCredentials(creds)) } else { opts = append (opts, grpc.WithInsecure()) } opts = append (opts, grpc.WithPerRPCCredentials(new (customCredential))) conn, err := grpc.Dial(Address, opts...) if err != nil { log.Fatalln(err) } defer conn.Close() c := pb.NewHelloClient(conn) req := &pb.HelloRequest{Name: "gRPC" } res, err := c.SayHello(context.Background(), req) if err != nil { log.Fatalln(err) } log.Println(res.Message) }
这里我们定义了一个customCredential
结构,并实现了两个方法GetRequestMetadata
和RequireTransportSecurity
。这是gRPC提供的自定义认证方式,每次RPC调用都会传输认证信息。customCredential
其实是实现了grpc/credential
包内的PerRPCCredentials
接口。每次调用,token信息会通过请求的metadata传输到服务端。下面具体看一下服务端如何获取metadata中的信息。
修改server/main.go中的SayHello方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package mainimport ( "fmt" "net" pb "hellogrpc/proto/hello" "context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "log" "google.golang.org/grpc/metadata" ) const ( Address = "127.0.0.1:50052" ) type helloService struct {}var HelloService = helloService{}func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.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 , fmt.Errorf("Token认证信息无效: appid=%s, appkey=%s ,Unauthenticated=%d" , appid, appkey,codes.Unauthenticated,) } resp := new (pb.HelloResponse) resp.Message = fmt.Sprintf("Hello %s.\nToken info: appid=%s,appkey=%s" , in.Name, appid, appkey) return resp, nil } func main () { listen, err := net.Listen("tcp" , Address) if err != nil { log.Fatalf("failed to listen: %v" , err) } creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem" , "../../keys/server.key" ) if err != nil { log.Fatalf("Failed to generate credentials %v" , err) } s := grpc.NewServer(grpc.Creds(creds)) pb.RegisterHelloServer(s, HelloService) log.Println("Listen on " + Address + " with TLS + Token" ) s.Serve(listen) }
服务端可以从context
中获取每次请求的metadata,从中读取客户端发送的token信息并验证有效性。
运行:
1 2 $ go run main.go Listen on 50052 with TLS + Token
运行客户端程序 client/main.go:
1 2 3 4 5 6 7 8 $ 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
google.golang.org/grpc/credentials/oauth
包已实现了用于Google API的oauth和jwt验证的方法,使用方法可以参考官方文档 。在实际应用中,我们可以根据自己的业务需求实现合适的验证方式。