func StreamServerInterceptor(authFunc AuthFunc) grpc.StreamServerInterceptor
func UnaryServerInterceptor(authFunc AuthFunc) grpc.UnaryServerInterceptor

type AuthFunc

type AuthFunc func(ctx context.Context) (context.Context, error) 验证方法

客户端请求添加bearer token

实现和上篇的自定义认证方法大同小异。gRPC 中默认定义了 PerRPCCredentials,是提供用于自定义认证的接口,它的作用是将所需的安全认证信息添加到每个RPC方法的上下文中。其包含 2 个方法:

  • GetRequestMetadata:获取当前请求认证所需的元数据
  • RequireTransportSecurity:是否需要基于 TLS 认证进行安全传输

接下来我们实现这两个方法

  1. // Token token认证
  2. type Token struct {
  3. Value string
  4. }
  5. //这里要以authorization为头部,和服务端对应
  6. const headerAuthorize string = "authorization"
  7. // GetRequestMetadata 获取当前请求认证所需的元数据
  8. func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
  9. return map[string]string{headerAuthorize: t.Value}, nil
  10. }
  11. // RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输
  12. func (t *Token) RequireTransportSecurity() bool {
  13. return true
  14. }

发送请求时添加token,注意:Token中的Value的形式要以bearer token值形式。因为我们服务端使用了bearer token验证方式。

  1. //从输入的证书文件中为客户端构造TLS凭证
  2. creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example")
  3. if err != nil {
  4. log.Fatalf("Failed to create TLS credentials %v", err)
  5. }
  6. //构建Token
  7. token := auth.Token{
  8. Value: "bearer grpc.auth.token",
  9. }
  10. // 连接服务器
  11. conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))

新建grpc_auth服务端拦截器

  1. // TokenInfo 用户信息
  2. type TokenInfo struct {
  3. ID string
  4. Roles []string
  5. }
  6. // AuthInterceptor 认证拦截器,对以authorization为头部,形式为`bearer token`的Token进行验证
  7. func AuthInterceptor(ctx context.Context) (context.Context, error) {
  8. token, err := grpc_auth.AuthFromMD(ctx, "bearer")
  9. if err != nil {
  10. return nil, err
  11. }
  12. tokenInfo, err := parseToken(token)
  13. if err != nil {
  14. return nil, grpc.Errorf(codes.Unauthenticated, " %v", err)
  15. }
  16. //使用context.WithValue添加了值后,可以用Value(key)方法获取值
  17. newCtx := context.WithValue(ctx, tokenInfo.ID, tokenInfo)
  18. //log.Println(newCtx.Value(tokenInfo.ID))
  19. return newCtx, nil
  20. }
  21. //解析token,并进行验证
  22. func parseToken(token string) (TokenInfo, error) {
  23. var tokenInfo TokenInfo
  24. if token == "grpc.auth.token" {
  25. tokenInfo.ID = "1"
  26. tokenInfo.Roles = []string{"admin"}
  27. return tokenInfo, nil
  28. }
  29. return tokenInfo, errors.New("Token无效: bearer " + token)
  30. }
  31. //从token中获取用户唯一标识
  32. func userClaimFromToken(tokenInfo TokenInfo) string {
  33. return tokenInfo.ID
  34. }
  • GetRequestMetadata:获取当前请求认证所需的元数据
  • RequireTransportSecurity:是否需要基于 TLS 认证进行安全传输

接下来我们实现这两个方法

  1. // Token token认证
  2. type Token struct {
  3. Value string
  4. }
  5. //这里要以authorization为头部,和服务端对应
  6. const headerAuthorize string = "authorization"
  7. // GetRequestMetadata 获取当前请求认证所需的元数据
  8. func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
  9. return map[string]string{headerAuthorize: t.Value}, nil
  10. }
  11. // RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输
  12. func (t *Token) RequireTransportSecurity() bool {
  13. return true
  14. }

发送请求时添加token,注意:Token中的Value的形式要以bearer token值形式。因为我们服务端使用了bearer token验证方式。

  1. //从输入的证书文件中为客户端构造TLS凭证
  2. creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example")
  3. if err != nil {
  4. log.Fatalf("Failed to create TLS credentials %v", err)
  5. }
  6. //构建Token
  7. token := auth.Token{
  8. Value: "bearer grpc.auth.token",
  9. }
  10. // 连接服务器
  11. conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))

把grpc_auth拦截器添加到服务端

  1. grpcServer := grpc.NewServer(cred.TLSInterceptor(),
  2. grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
  3. grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),
  4. grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),
  5. )),
  6. grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
  7. grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),
  8. grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),
  9. )),
  10. )

例子:

client

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "google.golang.org/grpc/credentials"
  6. "time"
  7. "github.com/sirupsen/logrus"
  8. "google.golang.org/grpc"
  9. pb "test/grpcLB/protos"
  10. )
  11. // Token token认证
  12. type Token struct {
  13. Value string
  14. }
  15. const headerAuthorize string = "authorization"
  16. // GetRequestMetadata 获取当前请求认证所需的元数据
  17. func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
  18. return map[string]string{headerAuthorize: t.Value}, nil
  19. }
  20. // RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输
  21. func (t *Token) RequireTransportSecurity() bool {
  22. return true
  23. }
  24. func main() {
  25. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  26. defer cancel()
  27. //TLS连接
  28. creds, err := credentials.NewClientTLSFromFile("./keys/server.pem", "go-grpc-example")
  29. if err != nil {
  30. panic(err.Error())
  31. }
  32. //构建Token
  33. token := Token{
  34. Value: "bearer grpc.auth.toke",
  35. }
  36. grpc.WithInsecure()
  37. // authority是自己随便起的,不是必须的,但是r.Scheme()+"://authority/"+*svc这种格式是必须的
  38. conn, err := grpc.DialContext(ctx, "localhost:50000", grpc.WithTransportCredentials(creds),grpc.WithPerRPCCredentials(&token))
  39. if err != nil {
  40. panic(err)
  41. }
  42. client := pb.NewGreeterClient(conn)
  43. resp, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "world "})
  44. if err == nil {
  45. logrus.Infof("Reply is %s\n", resp.Message)
  46. }else {
  47. fmt.Println(err)
  48. }
  49. }

service

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/grpc-ecosystem/go-grpc-middleware"
  6. "google.golang.org/grpc/codes"
  7. "google.golang.org/grpc/credentials"
  8. "google.golang.org/grpc/grpclog"
  9. "google.golang.org/grpc/status"
  10. "net"
  11. "time"
  12. "google.golang.org/grpc"
  13. "github.com/sirupsen/logrus"
  14. "github.com/grpc-ecosystem/go-grpc-middleware/auth"
  15. pb "test/grpcLB/protos"
  16. "errors"
  17. )
  18. const DialTimeout = time.Second*5
  19. // TokenInfo 用户信息
  20. type TokenInfo struct {
  21. ID string
  22. Roles []string
  23. }
  24. // AuthInterceptor 认证拦截器,对以authorization为头部,形式为`bearer token`的Token进行验证
  25. // 返回值 context.Context 会传到具体的service
  26. func AuthInterceptor(ctx context.Context) (context.Context, error) {
  27. token, err := grpc_auth.AuthFromMD(ctx, "bearer")
  28. if err != nil {
  29. return ctx, err
  30. }
  31. tokenInfo, err := parseToken(token)
  32. if err != nil {
  33. return ctx, status.Errorf(codes.Unauthenticated, " %v", err)
  34. }
  35. //使用context.WithValue添加了值后,可以用Value(key)方法获取值
  36. newCtx := context.WithValue(ctx, "id",tokenInfo.ID)
  37. newCtx = context.WithValue(newCtx, "role",tokenInfo.Roles)
  38. //log.Println(newCtx.Value(tokenInfo.ID))
  39. return newCtx, nil
  40. }
  41. //解析token,并进行验证
  42. func parseToken(token string) (TokenInfo, error) {
  43. var tokenInfo TokenInfo
  44. if token == "grpc.auth.token" {
  45. tokenInfo.ID = "1"
  46. tokenInfo.Roles = []string{"admin"}
  47. return tokenInfo, nil
  48. }
  49. return tokenInfo, errors.New("Token无效: bearer " + token)
  50. }
  51. //从token中获取用户唯一标识
  52. func userClaimFromToken(tokenInfo TokenInfo) string {
  53. return tokenInfo.ID
  54. }
  55. func main() {
  56. // 端口监听
  57. lis, err := net.Listen("tcp", ":50000")
  58. if err != nil {
  59. panic(err)
  60. }
  61. defer lis.Close()
  62. //TLS认证
  63. creds, err := credentials.NewServerTLSFromFile("./keys/server.pem", "./keys/server.key")
  64. if err != nil {
  65. grpclog.Fatal("加载在证书文件失败", err)
  66. }
  67. s := grpc.NewServer(grpc.Creds(creds),grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
  68. grpc_auth.StreamServerInterceptor(AuthInterceptor),
  69. )),
  70. grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
  71. grpc_auth.UnaryServerInterceptor(AuthInterceptor),
  72. )),)
  73. pb.RegisterGreeterServer(s, &server{})
  74. s.Serve(lis)
  75. }
  76. // server is used to implement helloworld.GreeterServer.
  77. type server struct{}
  78. // SayHello implements helloworld.GreeterServer
  79. func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  80. fmt.Println(ctx.Value("id"))
  81. fmt.Println(ctx.Value("role"))
  82. logrus.Infof("%v: Receive is %s\n", time.Now(), in.Name)
  83. return &pb.HelloReply{Message: "Hello " + in.Name }, nil
  84. }

成功:
image.png
image.png

失败:
client不携带token,或token值给个错的
image.png