抖声服务端开发-JWT认证中间件 | 青训营笔记

146 阅读3分钟

抖声服务端开发-JWT认证中间件 | 青训营笔记

这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记

什么是JWT

JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth2.0业务场景下。

JWT就是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,经过签名后得到一个Token(令牌)再发回给用户,用户后续请求只需要带上这个Token,服务端解密之后就能获取该用户的相关信息了。

生成和解析JWT

定义需求

我们需要定制自己的需求来决定JWT中保存哪些数据,比如我们规定在JWT中要存储usernameuserid信息,那么我们就定义一个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()
   }
}