最近在完善微服务网关,需要用Python生成JWT,而go去使用JWT。遇到了一个JSON反序列化的小问题,顺便记录一下。
# JWT
JWT想必大家都很熟悉了,我分别写过Java/Python和Go的版本,但大多数都是网上copy
的demo。
这里奉劝一下大家,网上的demo代码很少能开箱即用,不是说没有哈,有些demo会存在一定的代码缺陷或者风险
。copy代码,我们最后还得自己多测测多验证下,能跑就行
是个误区。
今天就给大家聊聊我的经历。
# copy代码
下面是我从简书/csdn copy的源码,我也差不多看了一下,感觉没啥问题,token也能正常解析
出来。
package auth
import (
"encoding/json"
"errors"
"github.com/dgrijalva/jwt-go"
"time"
)
var (
TokenExpired = errors.New("身份认证已过期, 请重新登录")
TokenNotValidYet = errors.New("身份认证未生效")
TokenMalformed = errors.New("登录状态已失效, 请重新登录")
TokenInvalid = errors.New("身份认证不合法")
)
type UserInfo struct {
ID int `json:"id"` // userId
Email string `json:"email"` // user email
Name string `json:"name"` // username
Role int `json:"role"`
}
type CustomClaims struct {
UserInfo
jwt.StandardClaims `json:"jwt,omitempty"`
}
func (c *UserInfo) Marshal() string {
b, _ := json.Marshal(c)
return string(b)
}
type JWT struct {
signKey []byte
}
func NewJWT(key string) *JWT {
return &JWT{[]byte(key)}
}
// GetSignKey get the sign key
func (j *JWT) GetSignKey() string {
return string(j.signKey)
}
// SetSignKey set sign key for jwt
func (j *JWT) SetSignKey(key string) {
j.signKey = []byte(key)
}
func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.signKey)
}
func (j *JWT) ParseToken(t string) (*CustomClaims, error) {
token, err := jwt.ParseWithClaims(t, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.signKey, nil
})
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, TokenMalformed
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
// Token is expired
return nil, TokenExpired
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
return nil, TokenNotValidYet
} else {
return nil, TokenInvalid
}
}
return nil, err
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, TokenInvalid
}
func (j *JWT) RefreshToken(tokenStr string) (string, error) {
jwt.TimeFunc = func() time.Time {
return time.Unix(0, 0)
}
token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.signKey, nil
})
if err != nil {
return "", err
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
jwt.TimeFunc = time.Now
claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
return j.CreateToken(*claims)
}
return "", TokenInvalid
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
上述代码里面,我仅仅只是加了一些错误类型
。起初还是能正常运行的,到后面我发现一个token可以无限使用
,也就是说token没有过期时间
。然而我的pyjwt是能正常解析出过期时间的,这就必然说明go版本的代码存在问题了。
# 排查问题
# 确定问题
首先我们要做的就是Debug嘛,看看到底是哪里不对劲
。JWT的数据内容一般分为2部分:
系统自带的
包括过期时间等等基础信息。
用户定义的
这里存放的就是真正的用户信息了。
我们新建一个jwt_test.go文件:
package auth
import "testing"
func TestJWT_ParseToken(t *testing.T) {
jt := NewJWT("你的token")
jt.ParseToken("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NjI0NDI1MTAsImlkIjoxLCJ1c2VybmFtZSI6Indvb2R5IiwibmFtZSI6Ilx1N2M3M1x1NmQxYiIsImVtYWlsIjoiNjE5NDM0MTc2QHFxLmNvbSIsInJvbGUiOjIsInBob25lIjoiMTg1MTY2MDA3MTYiLCJjcmVhdGVkX2F0IjoiMjAyMS0wMy0xNSAxNzowNDowMSIsInVwZGF0ZWRfYXQiOiIyMDIyLTA1LTA4IDAxOjE0OjMwIiwiZGVsZXRlZF9hdCI6MCwidXBkYXRlX3VzZXIiOjEsImxhc3RfbG9naW5fYXQiOiIyMDIyLTA5LTAzIDEzOjM1OjEwIiwiYXZhdGFyIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzExMjY2NzM4P3Y9NCIsImlzX3ZhbGlkIjp0cnVlfQ.4VM9f5mODxReoYQUXoTY_vX52lf-c8goOxnosy8o8Es")
}
2
3
4
5
6
7
8
9
接着我们只需要打下断点,查看信息即可:
可以看出,这个ExpireAt是0,也就是说没有正确解析到过期时间。这个也就是问题所在。
# 定位问题
问题已经确定好了,是expiredAt没有解析到,那么问题来了,我们是什么环节出了岔子呢?我们还得去源码里面一探究竟:
其实他就是一个JSON反序列化的操作,把我们的token字符串=>解密=>反序列化为JSON。
在Pyjwt里面,我们是用exp这个字段代表的过期时间,我们来看看反序列化的结构体里面是否也是这个字段:
可以看到完全对应,那就很奇怪了。到底问题出在哪了呢?
细看了CustomClaims之后,我发现了问题所在:
这里有个json标签(demo自带的源码),用它这个源码的话,生成的JSON会是这样的数据:
{"jwt": {"exp": 11111}, "role": 0, "email": "xxx.qq.com"}
也就是说,这个结构体内嵌
了一个结构体,如果给他加上json标签的话,CustomClaims的字段就变成了 userInfo的字段
+ jwt
去掉的话就是userInfo的字段
+ StandardClaims的字段
。
而我们的pyjwt,exp字段和用户字段是同级,也就是下图所示:
这肯定解析不到,我们试下去掉这个tag:
type CustomClaims struct {
UserInfo
jwt.StandardClaims
}
2
3
4
# 重新尝试
再次debug,发现数据有了,那就是这个反序列化字段的问题。
# 总结
由于demo代码给内部struct加上了tag,导致内部struct有了自己的名字(jwt),所以序列化对应的数据变成了这样:
{"jwt": {"exp": "123"}}
所以反序列化也解析不出来
数据。
最后的最后,警示大家,如果代码要上生产,切记一定要仔细测试/检查copy的残码
。