认证

gRPC默认内置了两种认证方式:

  • SSL/TLS认证方式

  • 基于Token的认证方式

同时,gRPC提供了接口用于扩展自定义认证方式

TLS认证示例

这里直接扩展hello项目,实现TLS认证机制

首先需要准备证书,在hello目录新建keys目录用于存放证书文件。

证书制作

制作私钥 (.key)

  1. # Key considerations for algorithm "RSA" ≥ 2048-bit
  2. $ openssl genrsa -out server.key 2048
  3. # Key considerations for algorithm "ECDSA" ≥ secp384r1
  4. # List ECDSA the supported curves (openssl ecparam -list_curves)
  5. $ 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. Country Name (2 letter code) [AU]:CN
  3. State or Province Name (full name) [Some-State]:XxXx
  4. Locality Name (eg, city) []:XxXx
  5. Organization Name (eg, company) [Internet Widgits Pty Ltd]:XX Co. Ltd
  6. Organizational Unit Name (eg, section) []:Dev
  7. Common Name (e.g. server FQDN or YOUR name) []:server name
  8. Email Address []:xxx@xxx.com

目录结构

  1. |—— hello-tls/
  2. |—— client/
  3. |—— main.go // 客户端
  4. |—— server/
  5. |—— main.go // 服务端
  6. |—— keys/ // 证书目录
  7. |—— server.key
  8. |—— server.pem
  9. |—— proto/
  10. |—— hello/
  11. |—— hello.proto // proto描述文件
  12. |—— hello.pb.go // proto编译后文件

示例代码

proto/helloworld.protoproto/hello.pb.go文件不需要改动

修改服务端代码:server/main.go

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. pb "github.com/jergoo/go-grpc-example/proto/hello"
  6. "golang.org/x/net/context"
  7. "google.golang.org/grpc"
  8. "google.golang.org/grpc/credentials" // 引入grpc认证包
  9. "google.golang.org/grpc/grpclog"
  10. )
  11. const (
  12. // Address gRPC服务地址
  13. Address = "127.0.0.1:50052"
  14. )
  15. // 定义helloService并实现约定的接口
  16. type helloService struct{}
  17. // HelloService Hello服务
  18. var HelloService = helloService{}
  19. // SayHello 实现Hello服务接口
  20. func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
  21. resp := new(pb.HelloResponse)
  22. resp.Message = fmt.Sprintf("Hello %s.", in.Name)
  23. return resp, nil
  24. }
  25. func main() {
  26. listen, err := net.Listen("tcp", Address)
  27. if err != nil {
  28. grpclog.Fatalf("Failed to listen: %v", err)
  29. }
  30. // TLS认证
  31. creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key")
  32. if err != nil {
  33. grpclog.Fatalf("Failed to generate credentials %v", err)
  34. }
  35. // 实例化grpc Server, 并开启TLS认证
  36. s := grpc.NewServer(grpc.Creds(creds))
  37. // 注册HelloService
  38. pb.RegisterHelloServer(s, HelloService)
  39. grpclog.Println("Listen on " + Address + " with TLS")
  40. s.Serve(listen)
  41. }

运行:

  1. $ go run main.go
  2. Listen on 127.0.0.1:50052 with TLS

服务端在实例化grpc Server时,可配置多种选项,TLS认证是其中之一

客户端添加TLS认证:client/main.go

  1. package main
  2. import (
  3. pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包
  4. "golang.org/x/net/context"
  5. "google.golang.org/grpc"
  6. "google.golang.org/grpc/credentials" // 引入grpc认证包
  7. "google.golang.org/grpc/grpclog"
  8. )
  9. const (
  10. // Address gRPC服务地址
  11. Address = "127.0.0.1:50052"
  12. )
  13. func main() {
  14. // TLS连接
  15. creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name")
  16. if err != nil {
  17. grpclog.Fatalf("Failed to create TLS credentials %v", err)
  18. }
  19. conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))
  20. if err != nil {
  21. grpclog.Fatalln(err)
  22. }
  23. defer conn.Close()
  24. // 初始化客户端
  25. c := pb.NewHelloClient(conn)
  26. // 调用方法
  27. req := &pb.HelloRequest{Name: "gRPC"}
  28. res, err := c.SayHello(context.Background(), req)
  29. if err != nil {
  30. grpclog.Fatalln(err)
  31. }
  32. grpclog.Println(res.Message)
  33. }

运行:

  1. $ go run main.go
  2. Hello gRPC

客户端添加TLS认证的方式和服务端类似,在创建连接Dial时,同样可以配置多种选项,后面的示例中会看到更多的选项。

Token认证示例

再进一步,继续扩展hello-tls项目,实现TLS + Token认证机制

目录结构

  1. |—— hello_token/
  2. |—— client/
  3. |—— main.go // 客户端
  4. |—— server/
  5. |—— main.go // 服务端
  6. |—— keys/ // 证书目录
  7. |—— server.key
  8. |—— server.pem
  9. |—— proto/
  10. |—— hello/
  11. |—— hello.proto // proto描述文件
  12. |—— hello.pb.go // proto编译后文件

示例代码

先修改客户端实现:client/main.go

  1. package main
  2. import (
  3. pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包
  4. "golang.org/x/net/context"
  5. "google.golang.org/grpc"
  6. "google.golang.org/grpc/credentials" // 引入grpc认证包
  7. "google.golang.org/grpc/grpclog"
  8. )
  9. const (
  10. // Address gRPC服务地址
  11. Address = "127.0.0.1:50052"
  12. // OpenTLS 是否开启TLS认证
  13. OpenTLS = true
  14. )
  15. // customCredential 自定义认证
  16. type customCredential struct{}
  17. // GetRequestMetadata 实现自定义认证接口
  18. func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
  19. return map[string]string{
  20. "appid": "101010",
  21. "appkey": "i am key",
  22. }, nil
  23. }
  24. // RequireTransportSecurity 自定义认证是否开启TLS
  25. func (c customCredential) RequireTransportSecurity() bool {
  26. return OpenTLS
  27. }
  28. func main() {
  29. var err error
  30. var opts []grpc.DialOption
  31. if OpenTLS {
  32. // TLS连接
  33. creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name")
  34. if err != nil {
  35. grpclog.Fatalf("Failed to create TLS credentials %v", err)
  36. }
  37. opts = append(opts, grpc.WithTransportCredentials(creds))
  38. } else {
  39. opts = append(opts, grpc.WithInsecure())
  40. }
  41. // 使用自定义认证
  42. opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
  43. conn, err := grpc.Dial(Address, opts...)
  44. if err != nil {
  45. grpclog.Fatalln(err)
  46. }
  47. defer conn.Close()
  48. // 初始化客户端
  49. c := pb.NewHelloClient(conn)
  50. // 调用方法
  51. req := &pb.HelloRequest{Name: "gRPC"}
  52. res, err := c.SayHello(context.Background(), req)
  53. if err != nil {
  54. grpclog.Fatalln(err)
  55. }
  56. grpclog.Println(res.Message)
  57. }

这里我们定义了一个customCredential结构,并实现了两个方法GetRequestMetadataRequireTransportSecurity。这是gRPC提供的自定义认证方式,每次RPC调用都会传输认证信息。customCredential其实是实现了grpc/credential包内的PerRPCCredentials接口。每次调用,token信息会通过请求的metadata传输到服务端。下面具体看一下服务端如何获取metadata中的信息。

修改server/main.go中的SayHello方法:

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. pb "github.com/jergoo/go-grpc-example/proto/hello"
  6. "golang.org/x/net/context"
  7. "google.golang.org/grpc"
  8. "google.golang.org/grpc/codes"
  9. "google.golang.org/grpc/credentials" // 引入grpc认证包
  10. "google.golang.org/grpc/grpclog"
  11. "google.golang.org/grpc/metadata" // 引入grpc meta包
  12. )
  13. const (
  14. // Address gRPC服务地址
  15. Address = "127.0.0.1:50052"
  16. )
  17. // 定义helloService并实现约定的接口
  18. type helloService struct{}
  19. // HelloService ...
  20. var HelloService = helloService{}
  21. // SayHello 实现Hello服务接口
  22. func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
  23. // 解析metada中的信息并验证
  24. md, ok := metadata.FromContext(ctx)
  25. if !ok {
  26. return nil, grpc.Errorf(codes.Unauthenticated, "无Token认证信息")
  27. }
  28. var (
  29. appid string
  30. appkey string
  31. )
  32. if val, ok := md["appid"]; ok {
  33. appid = val[0]
  34. }
  35. if val, ok := md["appkey"]; ok {
  36. appkey = val[0]
  37. }
  38. if appid != "101010" || appkey != "i am key" {
  39. return nil, grpc.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey)
  40. }
  41. resp := new(pb.HelloResponse)
  42. resp.Message = fmt.Sprintf("Hello %s.\nToken info: appid=%s,appkey=%s", in.Name, appid, appkey)
  43. return resp, nil
  44. }
  45. func main() {
  46. listen, err := net.Listen("tcp", Address)
  47. if err != nil {
  48. grpclog.Fatalf("failed to listen: %v", err)
  49. }
  50. // TLS认证
  51. creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key")
  52. if err != nil {
  53. grpclog.Fatalf("Failed to generate credentials %v", err)
  54. }
  55. // 实例化grpc Server, 并开启TLS认证
  56. s := grpc.NewServer(grpc.Creds(creds))
  57. // 注册HelloService
  58. pb.RegisterHelloServer(s, HelloService)
  59. grpclog.Println("Listen on " + Address + " with TLS + Token")
  60. s.Serve(listen)
  61. }

服务端可以从context中获取每次请求的metadata,从中读取客户端发送的token信息并验证有效性。

运行:

  1. $ go run main.go
  2. Listen on 50052 with TLS + Token

运行客户端程序 client/main.go:

  1. $ go run main.go
  2. // 认证成功结果
  3. Hello gRPC
  4. Token info: appid=101010,appkey=i am key
  5. // 修改key验证认证失败结果:
  6. rpc error: code = 16 desc = Token认证信息无效: appID=101010, appKey=i am not key

google.golang.org/grpc/credentials/oauth包已实现了用于Google API的oauth和jwt验证的方法,使用方法可以参考官方文档。在实际应用中,我们可以根据自己的业务需求实现合适的验证方式。