1. 认证

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

    • SSL/TLS认证方式

    • 基于Token的认证方式

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

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

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

    制作私钥 (.key)

    自签名公钥(x509) (PEM-encodings .pem|.crt)

    自定义信息

    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 //服务名称 例如www.topgoer.com
    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编译后文件

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

    运行:

    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连接 记得把server name改成你写的服务器地址
    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时,同样可以配置多种选项,后面的示例中会看到更多的选项。

    1.2. Token认证示例

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

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

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

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

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

    运行:

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

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