前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang笔记 6.4 JSON Web Tokens (JWT)

Golang笔记 6.4 JSON Web Tokens (JWT)

作者头像
twowinter
发布2020-04-17 18:11:49
7510
发布2020-04-17 18:11:49
举报
文章被收录于专栏:twowintertwowinter

1 JSON Web Tokens (JWT) 介绍

之前曾在 LoRaServer 笔记 2.4.1 JSON web-tokens 的使用 中学习了 JWT 的原理及其组成:JWT 是一个很长的字符串,xxxxx.yyyyy.zzzzz,中间用点(.)分隔成三个部分,依次为:Header(头部)、Payload(负载)、Signature(签名)。另外还学习使用 jwt.io 网站的调试工具。

go 中使用社区库 github.com/dgrijalva/jwt-go 来实现。

2 JWT 的代码实现

2.1 生成 token

官方示例 Simple example of building and signing a token

代码语言:javascript
复制
    // Create a new token object, specifying signing method and the claims
    // you would like it to contain.
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "foo": "bar",
        "nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
    })

    // Sign and get the complete encoded token as a string using the secret
    tokenString, err := token.SignedString(hmacSampleSecret)

    fmt.Println(tokenString, err)

显然 JWT 的 Header 由 jwt.SigningMethodHS256 确定,payload 则有 claim 确定,剩下签名则将密钥传入 token.SignedString,生成了最终 token。

2.2 解析 token

官方示例 Simple example of parsing and validating a token

代码语言:javascript
复制
    // sample token string taken from the New example
    tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU"

    // Parse takes the token string and a function for looking up the key. The latter is especially
    // useful if you use multiple keys for your application.  The standard is to use 'kid' in the
    // head of the token to identify which key to use, but the parsed token (head and claims) is provided
    // to the callback, providing flexibility.
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        // Don't forget to validate the alg is what you expect:
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
        }

        // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
        return hmacSampleSecret, nil
    })

    if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
        fmt.Println(claims["foo"], claims["nbf"])
    } else {
        fmt.Println(err)
    }

示例中还检查了 签名校验算法,HMAC 对应的是 HS 算法(我们的Header填了 HS256) 。可以在 jwt.io 中看到几种算法对应的签名校验方法。

3 gRPC helloworld demo 增加 JWT 认证

在使用 gRPC 时,token 是放在 metadata 中的相应 key 中。

本例中按照 LoRaServer 对 JWT 的格式要求来进行处理,metadata 中相应的 key 为 authorization。我们在笔记 6.3.1 gRPC 使用 metadata 自定义认证 的基础上,调整下 metadata 字段。

3.1 client

生成 token

这边自己造了一个新的 JWT,签名密钥使用 verysecret。

代码语言:javascript
复制
func createToken () (tokenString string) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "iss": "lora-app-server",
        "aud": "lora-app-server",
        "nbf": time.Now().Unix(),
        "exp": time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
        "sub": "user",
        "username": "admin"
    })
    tokenString, err := token.SignedString([]byte("verysecret"))
    return tokenString
}
更新 metadata
代码语言:javascript
复制
// customCredential 自定义认证
type customCredential struct{}

func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
        "authorization": createToken(),
    }, nil
}

func (c customCredential) RequireTransportSecurity() bool {
    return false
}

func main() {
    var opts []grpc.DialOption
	opts = append(opts, grpc.WithInsecure())
	opts = append(opts, grpc.WithBlock())
    // 使用自定义认证
    opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
	// Set up a connection to the server.
	conn, err := grpc.Dial(address, opts...)
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGreeterClient(conn)

	// Contact the server and print out its response.
	name := defaultName
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.GetMessage())
}

3.2 server

代码语言:javascript
复制
// 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 "", ErrNoMetadataInContext
	}
    // md 的类型是 type MD map[string][]string
	token, ok := md["authorization"]
	if !ok || len(token) == 0 {
		return "", ErrNoAuthorizationInMetadata
	}
    // 因此,token 是一个字符串数组,我们只用了 token[0]
	return token[0], nil
}

// Step2. 从 token 解析出 jwt 的 claim

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	tokenStr, err := getTokenFromContext(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "get token from context error")
	}

	token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		if token.Header["alg"] != "HS256" {
			return nil, ErrInvalidAlgorithm
		}
		return []byte("verysecret"), nil
	})
	if err != nil {
		return nil, errors.Wrap(err, "jwt parse error")
	}

	if !token.Valid {
		return nil, ErrInvalidToken
    }
    
	log.Printf("Received: %v\ntoken: %v", in.Name, token.Claims)
	return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

3.3 运行结果

代码语言:javascript
复制
# go run greeter_server/main.go
2019/11/15 14:47:34 Received: world
token: &{{lora-app-server 1577836800  0 lora-app-server 1573800454 user} admin}

可以看到从 token 解析出的 claim 是按照我们定义的结构体来呈现的:

代码语言:javascript
复制
type Claims struct {
	jwt.StandardClaims
	Username string `json:"username"`
}

4 小结

本篇笔记介绍 JWT 库的 DEMO 应用,还实现了一个比较常用的 gRPC JWT 认证的示例。

具体使用方法可简单记忆如下:

  • 在 jwt 生成时使用 jwt.NewWithClaims 方法,需传入 header claim实例 和 密钥。
  • 在 jwt 解析时使用 jwt.ParseWithClaims 方法,需传入 claim 结构体 和 密钥,可返回解析是否正确,及 token 是否有效。

END

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 JSON Web Tokens (JWT) 介绍
  • 2 JWT 的代码实现
    • 2.1 生成 token
      • 2.2 解析 token
      • 3 gRPC helloworld demo 增加 JWT 认证
        • 3.1 client
          • 生成 token
          • 更新 metadata
        • 3.2 server
          • 3.3 运行结果
          • 4 小结
          • END
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档