• 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:GoProjectsrcjwtdemo>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

  • 相关阅读:
    字符串时间+8个小时
    Django的field字段与参数介绍
    celery+Django
    二分查找
    排序算法
    socket套接字
    网络七层协议简述
    ORM操作mysql数据库多表的增删改查
    ORM操作mysql数据库
    Django框架静态文件配置和URL解析
  • 原文地址:https://www.cnblogs.com/majiang/p/14230060.html
Copyright © 2020-2023  润新知