这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
JSON Web Token(JWT)
JWT 是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任。
JWT 的结构
Herder.Paylod.Signature
-
Header: 包含 Token 类型和 Token 使用的算法的名称的 JSON Base64 字符串
-
Payload: 包含一些自定义信息,以及一些预定义的信息(如:exp 过期时间)的 JSON Base64 字符串
-
Signature: 将
Herder.Payload加上 secret 使用 Header 中算法得到的 HEX 字符串
可以在 JSON Web Token 可视化查看部分结构,以及各种语言的提供的 JWT 工具
Hertz 的 JWT 中间件 的使用
中间件的源码地址:hertz-contrib/jwt
登录流程:
- 调用
jwt.HertzJWTMiddleware对象的LoginHandler方法 - 进入
jwt.HertzJWTMiddleware对象的Authenticator方法,用来对登录请求进行处理,这里有两种方式处理- 直接在
Authenticator方法中处理登录请求,在收到登录请求后,在路由中直接调用LoginHandler方法 - 或者在
LoginHandler方法调用前将 JWT 需要包含的内容内容提前设置在app.RequestContext中
- 直接在
- 在
Authenticator中处理会有两种情况- 没有返回错误:这里返回的第一个参数会传递给
PayloadFunc方法,用来处理 JWT 的 Payload 部分需要包含哪些的自定义数据,然后内部执行完成后,会执行LoginResponse方法,响应登录成功的数据 - 返回了错误则会执行
HTTPStatusMessageFunc方法,然后通过这个方法返回对应错误的错误信息,后执行Unauthorized,在这个方法中返回错误信息到请求的客户端
- 没有返回错误:这里返回的第一个参数会传递给
认证流程:
- 注册
jwt.HertzJWTMiddleware的MiddlewareFunc方法返回的中间件到需要认证的接口 - 请求由中间件处理,会通过自定义的
IdentityHandler方法回去 Payload 中带的 Identity 值,并设置在app.RequestContext中 Key 是定义的IdentityKey值
代码:
JwtMiddleware, _ = jwtmw.New(&jwtmw.HertzJWTMiddleware{
Key: []byte(consts.JwtSecretKey),
TokenLookup: "header: Authorization, query: token, form: token", //cookie: jwt
TokenHeadName: "Bearer",
TimeFunc: time.Now,
Timeout: time.Hour,
MaxRefresh: time.Hour,
IdentityKey: consts.JwtIdentityKey,
// 设置从 token 提取用户信息的函数
IdentityHandler: func(ctx context.Context, req *app.RequestContext) interface{} {
claims := jwtmw.ExtractClaims(ctx, req)
return claims[consts.JwtIdentityKey]
},
// 设置登录时为 token 添加自定义负载信息的函数,如果不传入这个参数,则 token 的 payload 部分默认存储 token 的过期时间和创建时间
// data 对应 Authenticator 方法返回的第一个参数
PayloadFunc: func(data interface{}) jwtmw.MapClaims {
// 处理 Payload
if v, ok := data.(string); ok {
return jwtmw.MapClaims{
consts.JwtIdentityKey: v,
}
}
return jwtmw.MapClaims{}
},
// 认证用户的登录信息,配合 HertzJWTMiddleware.LoginHandler 使用
Authenticator: func(ctx context.Context, req *app.RequestContext) (interface{}, error) {
// 处理登录,返回需要保存到 Payload 中的数据
return "110", nil
},
// 设置登录的响应函数
LoginResponse: func(ctx context.Context, req *app.RequestContext, code int, token string, expire time.Time) {
req.JSON(http.StatusOK, utils.H{
"token": &token,
})
},
// 设置 jwt 授权失败后的响应
Unauthorized: func(ctx context.Context, req *app.RequestContext, code int, message string) {
req.JSON(code, utils.H{
"err": message
})
},
// 一旦 jwt 校验流程产生错误将执行,并返回错误的 message,然后执行 Unauthorized
HTTPStatusMessageFunc: func(e error, ctx context.Context, c *app.RequestContext) string {
// 之间将错误对应的信息返回
return e.Error()
},
})
部分代码解释:
- TokenLookup:在哪些部分去尝试读取 Token 一般情况下只用 header 一种就行了,在几天前的版本中还不支持在 form 中读取,现在支持了
- TokenHeadName:Header 中 JWT 的前缀,这里就是 "Bearer {{JWT}}"
如果某个接口,可以登录也可以不登录,可以自定义一个中间件然后判断是否存在 Token,或者使用我下面这种不太好的方案
func OptionalJwtMiddlewareFunc() app.HandlerFunc { return func(ctx context.Context, req *app.RequestContext) { _, err := JwtMiddleware.ParseToken(ctx, req) if errors.Is(err, jwtmw.ErrEmptyFormToken) || errors.Is(err, jwtmw.ErrEmptyQueryToken) || errors.Is(err, jwtmw.ErrEmptyAuthHeader) { return } if err != nil { msg := JwtMiddleware.HTTPStatusMessageFunc(err, ctx, req) JwtMiddleware.Unauthorized(ctx, req, 200, msg) req.Abort() return } JwtMiddleware.MiddlewareFunc()(ctx, req) } }