gRPC with JWT

梦见你的名字  金牌会员 | 2023-9-1 19:41:53 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 858|帖子 858|积分 2574

在 gRPC 中使用 JWT(JSON Web Tokens)进行身份验证是一种常见的做法,它可以帮助你确保请求方的身份和权限。下面是一种使用 gRPC 和 JWT 进行身份验证的步骤:

  • 生成和签发 JWT: 在用户登录成功后,你需要生成一个 JWT 并将其签发给用户。JWT 中可以包含一些有关用户身份、角色、权限等的信息。
  • 在 gRPC 的上下文中传递 JWT: 当客户端发送 gRPC 请求时,可以将 JWT 放置在 gRPC 请求的元数据(Metadata)中,作为请求的一部分。这样,服务器端就可以获取 JWT 并对其进行验证。
  • 服务器端验证 JWT: 在 gRPC 服务端,你需要编写代码来验证接收到的 JWT。这通常涉及到验证 JWT 的签名是否有效,以及检查其中的身份信息和权限等。
  • 决策和授权: 根据验证后的 JWT 信息,你可以决定是否允许用户继续访问请求的资源。这可能涉及到一些授权策略和业务逻辑。
以下是一个简单的示例,展示如何在 gRPC 中使用 JWT 进行身份验证:
proto文件

内容如下:
  1. syntax = "proto3";
  2. package chaincode.pb;
  3. option go_package = "./;pb";
  4. message HelloRequest { string name = 1; }
  5. message HelloResponse { string reply = 2; }
  6. service SayHi { rpc Hi(HelloRequest) returns (HelloResponse); }
复制代码
通过下面的命令生成相关的文件:
  1. $ protoc --go_out=./ --go-grpc_out=./ example.proto
  2. $ tree
  3. .
  4. ├── example_grpc.pb.go
  5. ├── example.pb.go
  6. └── example.proto
  7. 0 directories, 3 files
复制代码
server端

跟 client 端约定内容如下:

  • token有效期为半小时
  • iss使用gRPC token
  • sub使用gRPC example server
代码如下:
  1. package main
  2. import (
  3.         "chaincode/pb"
  4.         "context"
  5.         "fmt"
  6.         "net"
  7.         "time"
  8.         "github.com/golang-jwt/jwt/v5"
  9.         "github.com/pkg/errors"
  10.         "google.golang.org/grpc"
  11.         "google.golang.org/grpc/metadata"
  12. )
  13. var testKey = "testKey"
  14. type HiService struct {
  15.         pb.UnimplementedSayHiServer
  16. }
  17. func verifyToken(tokenString string) error {
  18.         token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
  19.                 return []byte(testKey), nil
  20.         })
  21.         if err != nil {
  22.                 return errors.Wrap(err, "init token parser error")
  23.         }
  24.         if !token.Valid {
  25.                 return errors.New("invalid token")
  26.         }
  27.         claims, ok := token.Claims.(jwt.MapClaims)
  28.         if !ok {
  29.                 return errors.New("invalid claims")
  30.         }
  31.         exp, err := claims.GetExpirationTime()
  32.         if err != nil {
  33.                 return errors.Wrap(err, "GetExpirationTime from token error")
  34.         }
  35.         now := time.Now()
  36.         if now.Sub(exp.Time) > 0 {
  37.                 return errors.New("the token expires")
  38.         }
  39.         if claims["sub"] != "gRPC example server" {
  40.                 return errors.New("invalid sub")
  41.         }
  42.         if claims["iss"] != "gRPC token" {
  43.                 return errors.New("invalid iss")
  44.         }
  45.         return nil
  46. }
  47. func (s *HiService) Hi(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
  48.         md, ok := metadata.FromIncomingContext(ctx)
  49.         if !ok {
  50.                 return nil, errors.New("token信息获取失败")
  51.         }
  52.         token := md.Get("Authorization")[0]
  53.         if err := verifyToken(token); err != nil {
  54.                 return nil, errors.Wrap(err, "token验证失败")
  55.         }
  56.         return &pb.HelloResponse{Reply: "hello " + req.Name}, nil
  57. }
  58. func main() {
  59.         // 创建grpc服务示例
  60.         sv := grpc.NewServer()
  61.         // 注册我们的服务
  62.         pb.RegisterSayHiServer(sv, new(HiService))
  63.         // 绑定端口,提供服务
  64.         lis, err := net.Listen("tcp", ":50001")
  65.         if err != nil {
  66.                 panic(err)
  67.         }
  68.         // 启动服务
  69.         fmt.Println("liston on: 50001")
  70.         sv.Serve(lis)
  71. }
复制代码
  1. $ go run main.go
  2. liston on: 50001
复制代码
client

代码如下:
  1. package main
  2. import (
  3.         "chaincode/pb"
  4.         "context"
  5.         "fmt"
  6.         "time"
  7.         "github.com/golang-jwt/jwt/v5"
  8.         "google.golang.org/grpc"
  9.         "google.golang.org/grpc/credentials/insecure"
  10. )
  11. var testKey = "testKey"
  12. func genToken() (string, error) {
  13.         claims := jwt.RegisteredClaims{
  14.                 ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)),
  15.                 Issuer:    "gRPC token",
  16.                 Subject:   "gRPC example client",
  17.         }
  18.         token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  19.         return token.SignedString([]byte(testKey))
  20. }
  21. type TokeAuth struct {
  22.         Token string
  23. }
  24. func (t *TokeAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
  25.         return map[string]string{
  26.                 "Authorization": t.Token,
  27.         }, nil
  28. }
  29. func (t *TokeAuth) RequireTransportSecurity() bool {
  30.         return false
  31. }
  32. func main() {
  33.         token, err := genToken()
  34.         if err != nil {
  35.                 panic(err)
  36.         }
  37.         conn, err := grpc.Dial("localhost:50001", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(&TokeAuth{Token: token}))
  38.         if err != nil {
  39.                 panic(err)
  40.         }
  41.         defer conn.Close()
  42.         client := pb.NewSayHiClient(conn)
  43.         resp, err := client.Hi(context.Background(), &pb.HelloRequest{Name: "Wang"})
  44.         if err != nil {
  45.                 panic(err)
  46.         }
  47.         fmt.Println(resp.String())
  48. }
复制代码
现在我们先将 client 端生成 token 的sub 设置为 gRPC example client,执行
  1. $ go run main.go
  2. panic: rpc error: code = Unknown desc = token验证失败: invalid sub
  3. goroutine 1 [running]:
  4. main.main()
  5.         /root/go/src/example/client/main.go:55 +0x2f2
  6. exit status 2
复制代码
再将 client 端生成 token 的sub 设置为 gRPC example server,执行
  1. $ go run main.go
  2. reply:"hello Wang"
复制代码
以上示例是一个简单的代码示例,实际上还需要处理错误、安全性和其他细节。
  
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦见你的名字

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表