gRPC with JWT

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

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

以下是一个简单的示例,展示如何在 gRPC 中使用 JWT 进行身份验证:

proto文件

内容如下:

syntax = "proto3";
package chaincode.pb;
option go_package = "./;pb";
message HelloRequest { string name = 1; }
message HelloResponse { string reply = 2; }
service SayHi { rpc Hi(HelloRequest) returns (HelloResponse); }
syntax = "proto3";

package chaincode.pb;

option go_package = "./;pb";

message HelloRequest { string name = 1; }
message HelloResponse { string reply = 2; }

service SayHi { rpc Hi(HelloRequest) returns (HelloResponse); }
syntax = "proto3"; package chaincode.pb; option go_package = "./;pb"; message HelloRequest { string name = 1; } message HelloResponse { string reply = 2; } service SayHi { rpc Hi(HelloRequest) returns (HelloResponse); }

通过下面的命令生成相关的文件:

$ protoc --go_out=./ --go-grpc_out=./ example.proto
$ tree
.
├── example_grpc.pb.go
├── example.pb.go
└── example.proto
0 directories, 3 files
$ protoc --go_out=./ --go-grpc_out=./ example.proto
$ tree
.
├── example_grpc.pb.go
├── example.pb.go
└── example.proto

0 directories, 3 files
$ protoc --go_out=./ --go-grpc_out=./ example.proto $ tree . ├── example_grpc.pb.go ├── example.pb.go └── example.proto 0 directories, 3 files

server端

跟 client 端约定内容如下:

  • token有效期为半小时
  • iss使用gRPC token
  • sub使用gRPC example server

代码如下:

package main
import (
"chaincode/pb"
"context"
"fmt"
"net"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
var testKey = "testKey"
type HiService struct {
pb.UnimplementedSayHiServer
}
func verifyToken(tokenString string) error {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(testKey), nil
})
if err != nil {
return errors.Wrap(err, "init token parser error")
}
if !token.Valid {
return errors.New("invalid token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return errors.New("invalid claims")
}
exp, err := claims.GetExpirationTime()
if err != nil {
return errors.Wrap(err, "GetExpirationTime from token error")
}
now := time.Now()
if now.Sub(exp.Time) > 0 {
return errors.New("the token expires")
}
if claims["sub"] != "gRPC example server" {
return errors.New("invalid sub")
}
if claims["iss"] != "gRPC token" {
return errors.New("invalid iss")
}
return nil
}
func (s *HiService) Hi(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, errors.New("token信息获取失败")
}
token := md.Get("Authorization")[0]
if err := verifyToken(token); err != nil {
return nil, errors.Wrap(err, "token验证失败")
}
return &pb.HelloResponse{Reply: "hello " + req.Name}, nil
}
func main() {
// 创建grpc服务示例
sv := grpc.NewServer()
// 注册我们的服务
pb.RegisterSayHiServer(sv, new(HiService))
// 绑定端口,提供服务
lis, err := net.Listen("tcp", ":50001")
if err != nil {
panic(err)
}
// 启动服务
fmt.Println("liston on: 50001")
sv.Serve(lis)
}
package main

import (
  "chaincode/pb"
  "context"
  "fmt"
  "net"
  "time"

  "github.com/golang-jwt/jwt/v5"
  "github.com/pkg/errors"
  "google.golang.org/grpc"
  "google.golang.org/grpc/metadata"
)

var testKey = "testKey"

type HiService struct {
  pb.UnimplementedSayHiServer
}

func verifyToken(tokenString string) error {
  token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    return []byte(testKey), nil
  })
  if err != nil {
    return errors.Wrap(err, "init token parser error")
  }
  if !token.Valid {
    return errors.New("invalid token")
  }
  claims, ok := token.Claims.(jwt.MapClaims)
  if !ok {
    return errors.New("invalid claims")
  }
  exp, err := claims.GetExpirationTime()
  if err != nil {
    return errors.Wrap(err, "GetExpirationTime from token error")
  }

  now := time.Now()
  if now.Sub(exp.Time) > 0 {
    return errors.New("the token expires")
  }
  if claims["sub"] != "gRPC example server" {
    return errors.New("invalid sub")
  }
  if claims["iss"] != "gRPC token" {
    return errors.New("invalid iss")
  }
  return nil
}

func (s *HiService) Hi(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {

  md, ok := metadata.FromIncomingContext(ctx)
  if !ok {
    return nil, errors.New("token信息获取失败")
  }
  token := md.Get("Authorization")[0]
  if err := verifyToken(token); err != nil {
    return nil, errors.Wrap(err, "token验证失败")
  }

  return &pb.HelloResponse{Reply: "hello " + req.Name}, nil
}

func main() {
  // 创建grpc服务示例
  sv := grpc.NewServer()
  // 注册我们的服务
  pb.RegisterSayHiServer(sv, new(HiService))

  // 绑定端口,提供服务
  lis, err := net.Listen("tcp", ":50001")
  if err != nil {
    panic(err)
  }
  // 启动服务
  fmt.Println("liston on: 50001")
  sv.Serve(lis)
}
package main import ( "chaincode/pb" "context" "fmt" "net" "time" "github.com/golang-jwt/jwt/v5" "github.com/pkg/errors" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) var testKey = "testKey" type HiService struct { pb.UnimplementedSayHiServer } func verifyToken(tokenString string) error { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return []byte(testKey), nil }) if err != nil { return errors.Wrap(err, "init token parser error") } if !token.Valid { return errors.New("invalid token") } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return errors.New("invalid claims") } exp, err := claims.GetExpirationTime() if err != nil { return errors.Wrap(err, "GetExpirationTime from token error") } now := time.Now() if now.Sub(exp.Time) > 0 { return errors.New("the token expires") } if claims["sub"] != "gRPC example server" { return errors.New("invalid sub") } if claims["iss"] != "gRPC token" { return errors.New("invalid iss") } return nil } func (s *HiService) Hi(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, errors.New("token信息获取失败") } token := md.Get("Authorization")[0] if err := verifyToken(token); err != nil { return nil, errors.Wrap(err, "token验证失败") } return &pb.HelloResponse{Reply: "hello " + req.Name}, nil } func main() { // 创建grpc服务示例 sv := grpc.NewServer() // 注册我们的服务 pb.RegisterSayHiServer(sv, new(HiService)) // 绑定端口,提供服务 lis, err := net.Listen("tcp", ":50001") if err != nil { panic(err) } // 启动服务 fmt.Println("liston on: 50001") sv.Serve(lis) }
$ go run main.go
liston on: 50001
$ go run main.go
liston on: 50001
$ go run main.go liston on: 50001

client

代码如下:

package main
import (
"chaincode/pb"
"context"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var testKey = "testKey"
func genToken() (string, error) {
claims := jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)),
Issuer: "gRPC token",
Subject: "gRPC example client",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(testKey))
}
type TokeAuth struct {
Token string
}
func (t *TokeAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"Authorization": t.Token,
}, nil
}
func (t *TokeAuth) RequireTransportSecurity() bool {
return false
}
func main() {
token, err := genToken()
if err != nil {
panic(err)
}
conn, err := grpc.Dial("localhost:50001", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(&TokeAuth{Token: token}))
if err != nil {
panic(err)
}
defer conn.Close()
client := pb.NewSayHiClient(conn)
resp, err := client.Hi(context.Background(), &pb.HelloRequest{Name: "Wang"})
if err != nil {
panic(err)
}
fmt.Println(resp.String())
}
package main

import (
  "chaincode/pb"
  "context"
  "fmt"
  "time"

  "github.com/golang-jwt/jwt/v5"
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/insecure"
)

var testKey = "testKey"

func genToken() (string, error) {
  claims := jwt.RegisteredClaims{
    ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)),
    Issuer:    "gRPC token",
    Subject:   "gRPC example client",
  }
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  return token.SignedString([]byte(testKey))
}

type TokeAuth struct {
  Token string
}

func (t *TokeAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
  return map[string]string{
    "Authorization": t.Token,
  }, nil
}

func (t *TokeAuth) RequireTransportSecurity() bool {
  return false
}

func main() {
  token, err := genToken()
  if err != nil {
    panic(err)
  }
  conn, err := grpc.Dial("localhost:50001", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(&TokeAuth{Token: token}))
  if err != nil {
    panic(err)
  }
  defer conn.Close()

  client := pb.NewSayHiClient(conn)

  resp, err := client.Hi(context.Background(), &pb.HelloRequest{Name: "Wang"})
  if err != nil {
    panic(err)
  }
  fmt.Println(resp.String())
}
package main import ( "chaincode/pb" "context" "fmt" "time" "github.com/golang-jwt/jwt/v5" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) var testKey = "testKey" func genToken() (string, error) { claims := jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)), Issuer: "gRPC token", Subject: "gRPC example client", } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(testKey)) } type TokeAuth struct { Token string } func (t *TokeAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{ "Authorization": t.Token, }, nil } func (t *TokeAuth) RequireTransportSecurity() bool { return false } func main() { token, err := genToken() if err != nil { panic(err) } conn, err := grpc.Dial("localhost:50001", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(&TokeAuth{Token: token})) if err != nil { panic(err) } defer conn.Close() client := pb.NewSayHiClient(conn) resp, err := client.Hi(context.Background(), &pb.HelloRequest{Name: "Wang"}) if err != nil { panic(err) } fmt.Println(resp.String()) }

现在我们先将 client 端生成 token 的sub 设置为 gRPC example client,执行

$ go run main.go
panic: rpc error: code = Unknown desc = token验证失败: invalid sub
goroutine 1 [running]:
main.main()
/root/go/src/example/client/main.go:55 +0x2f2
exit status 2
$ go run main.go
panic: rpc error: code = Unknown desc = token验证失败: invalid sub

goroutine 1 [running]:
main.main()
        /root/go/src/example/client/main.go:55 +0x2f2
exit status 2
$ go run main.go panic: rpc error: code = Unknown desc = token验证失败: invalid sub goroutine 1 [running]: main.main() /root/go/src/example/client/main.go:55 +0x2f2 exit status 2

再将 client 端生成 token 的sub 设置为 gRPC example server,执行

$ go run main.go
reply:"hello Wang"
$ go run main.go
reply:"hello Wang"
$ go run main.go reply:"hello Wang"

以上示例是一个简单的代码示例,实际上还需要处理错误、安全性和其他细节。


声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意


© 版权声明
THE END
支持一下吧
点赞11 分享
评论 共2条
头像
请文明发言!
提交
头像

昵称

取消
昵称表情代码快捷回复
    • 头像binance0
    • 头像open a binance account0