抖声服务端开发-JWT认证中间件 | 青训营笔记
这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记
什么是JWT
JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth2.0业务场景下。
JWT就是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,经过签名后得到一个Token(令牌)再发回给用户,用户后续请求只需要带上这个Token,服务端解密之后就能获取该用户的相关信息了。
生成和解析JWT
定义需求
我们需要定制自己的需求来决定JWT中保存哪些数据,比如我们规定在JWT中要存储username和userid信息,那么我们就定义一个DouShengClaims结构体如下:
type DouShengClaims struct {
UserName string `json:"username"`
UserID int64 `json:"userid"`
jwt.StandardClaims
}
生成JWT
// GenerateToken 生成用户鉴权
// 用户名 和 用户id
func GenerateToken(username string, userid int64) (token string, err error) {
// token过期时间 24小时 * 天
dsc := DouShengClaims{
UserName: username,
UserID: userid,
StandardClaims: jwt.StandardClaims{
ExpiresAt: tokenExpiresAt(), // 过期时间
Issuer: issuer, // 签发人
},
}
// 使用指定的签名方法创建签名对象
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, dsc)
token, err = tokenClaims.SignedString(secret)
return
}
解析JWT
// ParseToken 解析JWT
func ParseToken(token string) (*DouShengClaims, error) {
dsc := new(DouShengClaims) // 存放解析出来的数据
tokenClaims, err := jwt.ParseWithClaims(token, dsc, func(token *jwt.Token) (interface{}, error) {
return secret, nil
})
if err != nil {
return nil, err
}
if !tokenClaims.Valid {
return nil, errdeal.CodeInvalidTokenErr
}
return dsc, nil
}
在项目中Gin中使用JWT认证中间件
用户在发布视频,点赞评论视频的时候都会携带token以区分用户身份和用户是否登陆,但抖声项目在发起请求时携带的token参数位置并不固定,大部分请求参数都在URL里,然而发布视频时携带的Token在form-data中,为了统一JWT验证中间件,本项目额的做法如下:
例如评论
// 评论 从url中获取token
r.POST("/douyin/comment/action/",
middlewares.JwtTokenMiddleware(middlewares.URLToken("token")),
handler.CommentActionHandler)
例如发布视频
// 视频发布 从form-data中获取token formKey : form-data的标识名
r.POST("/douyin/publish/action/",
middlewares.JwtTokenMiddleware(middlewares.FormToken("token")),
handler.VideoPublishHandler)
例如发布评论时,从URL中获取Token参数,因此在使用中间件时我们传入一个从URL中获取Token的方法,且Query参数为“token”。在发布视频时从form-data中获取Token参数。只需要传入不同的获取Token的方法即可。 下面看这些方法的具体实现:
定义一获取Token 的接口,有一个获取token的方法,返回token
// ContextTokenFunc 获取token的接口 可自己实现
type ContextTokenFunc interface {
QueryToken(*gin.Context) string
}
实现URLToken:
// 从url中获取token
type urlToken struct {
urlParam string // URL 中Token的参数名
}
func (u *urlToken) QueryToken(c *gin.Context) string {
return c.Query(u.urlParam) // 直接获取
}
// URLToken 从URL中获取token
func URLToken(urlParam string) ContextTokenFunc {
return &urlToken{
urlParam: urlParam,
}
}
实现FormToken:
type formToken struct {
formParam string // form-data的标识名
TokenType *struct {
Token string `json:"token"`
}
}
func (f *formToken) QueryToken(c *gin.Context) string {
token := c.PostForm(f.formParam)
// 反序列化到结构体中 即便出错 token默认值为 “”
//_ = json.Unmarshal([]byte(req), f.TokenType)
//return f.TokenType.Token
return token
}
// FormToken 从form-data中获取token
func FormToken(formParam string) ContextTokenFunc {
return &formToken{
formParam: formParam,
TokenType: &struct {
Token string `json:"token"`
}{Token: ""},
}
}
JWT 中间件:
// JwtTokenMiddleware token验证的中间件
// tokenFunc 获取token的方式 已实现从url和form-data中获取token
func JwtTokenMiddleware(ctf ContextTokenFunc) gin.HandlerFunc {
return func(c *gin.Context) {
// 获取请求中的token
token := ctf.QueryToken(c)
if len(token) == 0 { // 没有token
c.JSON(http.StatusOK, errdeal.NewResponse(errdeal.CodeWithoutTokenErr))
c.Abort()
return
}
// 标准的Token格式
// Bearer xxx.xxx.xxx
// Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6IuW8oOS4iSIsImV4cCI6MTY0ODUzMTI0OSwiaXNzIjoiYmx1ZWJlbGwuY29tIn0.15XVaBOhwKqrjhQpI1RKAVL0vqdWKHHnYNtAIBPt-RM
// 这里对客户端上传的token进行分割
//parts := strings.SplitN(token, " ", 2)
//if !(len(parts) == 2 || parts[0] == "Bearer") { //说明token格式不正确
// c.JSON(http.StatusOK, errdeal.NewResponse(errdeal.CodeInvalidTokenErr)) // 无效的token
// c.Abort()
// return
//}
// 解析token
parseToken, err := jwt.ParseToken(token)
if err != nil {
c.JSON(http.StatusOK, errdeal.NewResponse(errdeal.CodeInvalidTokenErr)) // 无效的token
c.Abort()
return
}
// 中间件判断用户登录的话 将用户id保存在 context里面 保证后续可以获取到
c.Set(ContextUserID, parseToken.UserID)
c.Next()
}
}