Go Grpc Jwt身份認證


在 http 請求當中我們可以設置 header 用來傳遞數據,grpc 底層采用 http2 協議也是支持傳遞數據的,采用的是 metadata。 Metadata 對於 gRPC 本身來說透明, 它使得 client 和 server 能為對方提供本次調用的信息。就像一次 http 請求的 RequestHeader 和 ResponseHeader,http header 的生命周期是一次 http 請求, Metadata 的生命周期則是一次 RPC 調用。在 go 語言中,可以用 grpc.WithPerRPCCredentials 方法來實現。

簡單說一下 我的demo吧,一般我們的api有一個login, 會返回token, 然后在請求及其他api的時候 就必須帶上這個token。

首先我們創建一個項目文件夾 jwttoken,里面在創建一個api文件夾

1.創建/api/api.proto【為了簡單我們沒有引入其他的包】

syntax = "proto3";
package api;
 
 
service Ping {
  rpc Login (LoginRequest) returns (LoginReply) {}
  rpc SayHello(PingMessage) returns (PingMessage) {}
}
 
message LoginRequest{
  string username=1;
  string password=2;
}
message LoginReply{
  string status=1;
  string token=2;
}
message PingMessage {
  string greeting = 1;
}

2.編譯該文件, 我一般習慣在根目錄下操作:

protoc -I api/ -I${GOPATH}/src --go_out=plugins=grpc:api api/api.proto

3.編寫api/handler.go 可以理解是我們日常服務的具體實現:

package api
 
import (
    "fmt"
 
    "golang.org/x/net/context"
)
 
// Server represents the gRPC server
type Server struct {
}
 
func (s *Server) Login(ctx context.Context, in *LoginRequest) (*LoginReply, error) {
    fmt.Println("Loginrequest: ", in.Username)
    if in.Username == "gavin" && in.Password == "gavin" {
        tokenString := CreateToken(in.Username)
        return &LoginReply{Status: "200", Token: tokenString}, nil
 
    } else {
        return &LoginReply{Status: "403", Token: ""}, nil
    }
 
}
 
// SayHello generates response to a Ping request
func (s *Server) SayHello(ctx context.Context, in *PingMessage) (*PingMessage, error) {
    msg := "bar"
    userName := CheckAuth(ctx)
    msg += " " + userName
    return &PingMessage{Greeting: msg}, nil
}

這個方法也很簡單, 驗證用戶名和密碼后 調用CreateToken 方法生成token,一般我們驗證token 主要是獲取 我們登陸的用戶名,所以CheckAuth方法返回登錄名,

4.具體實現在/api/authtoken.go方法里面:

package api
 
import (
    "context"
    "fmt"
    "time"
 
    "github.com/dgrijalva/jwt-go"
    "google.golang.org/grpc/metadata"
)
 
func CreateToken(userName string) (tokenString string) {
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "iss":      "lora-app-server",
        "aud":      "lora-app-server",
        "nbf":      time.Now().Unix(),
        "exp":      time.Now().Add(time.Hour).Unix(),
        "sub":      "user",
        "username": userName,
    })
    tokenString, err := token.SignedString([]byte("verysecret"))
    if err != nil {
        panic(err)
    }
    return tokenString
}
 
// AuthToekn 自定義認證
type AuthToekn struct {
    Token string
}
 
func (c AuthToekn) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
    return map[string]string{
        "authorization": c.Token,
    }, nil
}
 
func (c AuthToekn) RequireTransportSecurity() bool {
    return false
}
 
// Claims defines the struct containing the token claims.
type Claims struct {
    jwt.StandardClaims
 
    // Username defines the identity of the user.
    Username string `json:"username"`
}
 
// Step1. 從 context 的 metadata 中,取出 token
 
func getTokenFromContext(ctx context.Context) (string, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return "", fmt.Errorf("ErrNoMetadataInContext")
    }
    // md 的類型是 type MD map[string][]string
    token, ok := md["authorization"]
    if !ok || len(token) == 0 {
        return "", fmt.Errorf("ErrNoAuthorizationInMetadata")
    }
    // 因此,token 是一個字符串數組,我們只用了 token[0]
    return token[0], nil
}
 
func CheckAuth(ctx context.Context) (username string) {
    tokenStr, err := getTokenFromContext(ctx)
    if err != nil {
        panic("get token from context error")
    }
    var clientClaims Claims
    token, err := jwt.ParseWithClaims(tokenStr, &clientClaims, func(token *jwt.Token) (interface{}, error) {
        if token.Header["alg"] != "HS256" {
            panic("ErrInvalidAlgorithm")
        }
        return []byte("verysecret"), nil
    })
    if err != nil {
        panic("jwt parse error")
    }
 
    if !token.Valid {
        panic("ErrInvalidToken")
    }
 
    return clientClaims.Username
}

其中AuthToekn實現了PerRPCCredentials接口【需要實現GetRequestMetadata 和 RequireTransportSecurity方法】

5.編寫main.go 【由於是簡單的demo,我習慣吧客戶端 和服務端放在一起】

package main
 
import (
    "context"
    "fmt"
    "log"
    "net"
 
    "jwtdemo/api"
 
    "google.golang.org/grpc"
)
 
func main() {
    go GrpcServer()
    go GrpcClient()
    var a string
    fmt.Scan(&a)
}
 
// main start a gRPC server and waits for connection
func GrpcServer() {
    // create a listener on TCP port 7777
    lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 7777))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // create a server instance
    s := api.Server{}
    // create a gRPC server object
    grpcServer := grpc.NewServer()
    // attach the Ping service to the server
    api.RegisterPingServer(grpcServer, &s)
    // start the server
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %s", err)
    }
}
func GrpcClient() {
    var conn *grpc.ClientConn
    //call Login
    conn, err := grpc.Dial(":7777", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %s", err)
    }
    defer conn.Close()
    c := api.NewPingClient(conn)
    loginReply, err := c.Login(context.Background(), &api.LoginRequest{Username: "gavin", Password: "gavin"})
    if err != nil {
        log.Fatalf("Error when calling SayHello: %s", err)
    }
    fmt.Println("Login Reply:", loginReply)
    //Call SayHello
    requestToken := new(api.AuthToekn)
    requestToken.Token = loginReply.Token
    conn, err = grpc.Dial(":7777", grpc.WithInsecure(), grpc.WithPerRPCCredentials(requestToken))
    if err != nil {
        log.Fatalf("did not connect: %s", err)
    }
    defer conn.Close()
    c = api.NewPingClient(conn)
    helloreply, err := c.SayHello(context.Background(), &api.PingMessage{Greeting: "foo"})
    if err != nil {
        log.Fatalf("Error when calling SayHello: %s", err)
    }
    log.Printf("Response from server: %s", helloreply.Greeting)
}

注意 這個的客戶端代碼 c = api.NewPingClient(conn) 而不是我以前  c := &Server{} 【這里要傳遞conn,不然后端無法獲取token】,

運行結果如下 login 正常返回token, 在請求hello的時候 代入token, 后端正常獲取到【后面再嘗試gateway的集成 以及雙向認證】

 

 
D:\GoProject\src\jwtdemo>go run main.go
Loginrequest:  gavin
Login Reply: status:"200" token:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJsb3JhLWFwcC1zZXJ2ZXIiLCJleHAiOjE2MDk3NDgxMzgsImlzcyI6ImxvcmEtYXBwLXNlcnZlciIsIm5iZiI6MTYwOTc0NDUzOCwic3ViIjoidXNlciIsInVzZXJuYW1lIjoiZ2F2aW4ifQ.jP-DjlFKNkS1o9KtnQuqdQMDk6ljd_8UK036mGD9m5o"
2021/01/04 15:15:38 Response from server: bar gavin

 下載地址 https://github.com/dz45693/gogrpcjwt.git   https://download.csdn.net/download/dz45693/14021638

參考:

https://medium.com/pantomath/how-we-use-grpc-to-build-a-client-server-system-in-go-dd20045fa1c2

https://blog.csdn.net/iotisan/article/details/103056622

https://blog.csdn.net/iotisan/article/details/103085973


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM