在 gRPC 中使用 JWT(JSON Web Tokens)進行身份驗證是一種常見的做法,它可以幫助你確保請求方的身份和許可權。下麵是一種使用 gRPC 和 JWT 進行身份驗證的步驟: 1. **生成和簽發 JWT:** 在用戶登錄成功後,你需要生成一個 JWT 並將其簽發給用戶。JWT 中可以包 ...
在 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文件
內容如下:
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
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)
}
$ 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())
}
現在我們先將 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
再將 client 端生成 token 的sub
設置為 gRPC example server
,執行
$ go run main.go
reply:"hello Wang"
以上示例是一個簡單的代碼示例,實際上還需要處理錯誤、安全性和其他細節。
聲明:本作品採用署名-非商業性使用-相同方式共用 4.0 國際 (CC BY-NC-SA 4.0)進行許可,使用時請註明出處。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 戀水無意