这是我参与「第五届青训营 」笔记创作活动的第11天。
之前发文介绍了Hertz中的Keyauth认证方法,这种办法限制较少,但是如果用JWT认证的话,需要自己实现token的签发与解析。现在来介绍一下JWT中间件,顾名思义,这就是专门用来实现JWT认证的,自带了签发与解析功能,但是会存在一些限制。
先来看一下中间件的实现流程:
func (mw *HertzJWTMiddleware) middlewareImpl(ctx context.Context, c *app.RequestContext) {
claims, err := mw.GetClaimsFromJWT(ctx, c)
if err != nil {
mw.unauthorized(ctx, c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, ctx, c))
return
}
switch v := claims["exp"].(type) {
//判断是否过期
}
c.Set("JWT_PAYLOAD", claims)
identity := mw.IdentityHandler(ctx, c)
if identity != nil {
c.Set(mw.IdentityKey, identity)
}
if !mw.Authorizator(identity, ctx, c) {
mw.unauthorized(ctx, c, http.StatusForbidden, mw.HTTPStatusMessageFunc(ErrForbidden, ctx, c))
return
}
c.Next(ctx)
}
首先,中间件会从用户请求中提取Claim,若成功提取则判断其是否过期。然后将Claim存入key为"JWT_PAYLOAD"的上下文中,将身份信息(用户自定义)存入key为mw.IdentityKey的上下文中,便于之后的处理函数获取。最后中间件根据用户自定义的身份验证函数验证用户信息,若通过则执行下一个处理函数。
相比keyauth来说,需要用户自己编写的部分少了很多:
mid, _ := jwt.New(&jwt.HertzJWTMiddleware{
Realm: "douyin",
Key: []byte(key),
IdentityKey: "uid",
PayloadFunc: func(data interface{}) jwt.MapClaims {
return jwt.MapClaims{"uid": strconv.FormatInt(data.(*domain.TokenClaims).Uid, 10)}
},
IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {
claims := jwt.ExtractClaims(ctx, c)
i, _ := strconv.ParseInt(claims["uid"].(string), 10, 64)
return i
},
TokenLookup: "form:token, query: token",
Unauthorized: func(ctx context.Context, c *app.RequestContext, code int, message string) {
c.JSON(code, map[string]interface{}{
"code": code,
"message": message,
})
},
})
- Key是用于JWT加密的密钥。
- IdentityKey是把Identity存入上下文所用的key,即,之后的处理函数中可以用ctx.get(IdentityKey)的方式来获取之。
- PayloadFunc是存储在claim中的信息,此处存储了用户的uid。
- IdentityHandler用于从解密的claim中提取用于验证所需的Identity,此处提取了用户的uid。
- TokenLookup指从哪里获取token,此处从form和query的token字段中获取。
- Unauthorized指要是验证不通过后要做什么。
还有一个问题就是,在登录函数里要怎么把token放进响应头中。JWT中间件为我们提供了一个登录函数模板,但是用别人的东西毕竟不太顺手。实际上只需要在自己的登录函数里调用中间件的TokenGenerator(claims)方法即可拿到token。