gin框架学习之JWT中间件| 青训营

70 阅读3分钟

JWT是什么

先来介绍一下什么是cookie和session:

cookie

由服务器生成,发送给浏览器,浏览器以键值对的方式保存下来,下次发送请求的时候带上cookie保存的信息传给客服务器。
缺点:每个域名下可使用数量少,大小也有限制。

session

由服务器生成,服务器保存主体信息,会发送一个sessionid给客户端cookie保存,下次发送请求时带上sessionid传给服务端,服务端根据sessionid和主体信息进行对比验证。
缺点:
cookie+session在跨域场景很麻烦; 如果是分布式部署,需要做多机共享session机制; 基于cookie的机制容易被CSRF; 查询session信息可能会有数据库查询操作,带来性能问题。

JWT

和token有点类似,在服务端加入了一个secret密钥,由用户发送用户名密码给服务端,服务端验证,成功之后就生成三个部分header,payload,signature组成的jwt token给客户端,之后的请求都带上 jwt token,服务端通过secret密钥进行验证。

jwt库的用法

1.生成jwt token

type MyClaims struct {
   Username string `json:"username"`
   jwt.StandardClaims
}

const TokenExpireDuration = time.Hour * 24 //设置过期时间

var Secret = []byte("secret") //密码自行设定

func GenToken(username string) (string, error) {
   // 创建一个我们自己的声明
   c := MyClaims{
      username, // 自定义字段
      jwt.StandardClaims{
         ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
         Issuer:    "superxon",                                 // 签发人
      },
   }
   // 使用指定的签名方法创建签名对象
   token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
   // 使用指定的secret签名并获得完整的编码后的字符串token
   return token.SignedString(Secret)
}

2.将生成的token返回给客户端

type Profile struct {
   Username    string `db:"username"`
   Password    string `db:"password"`
}

func AuthLoginHandler(c *gin.Context) {
   // 用户发送用户名和密码过来
   var user User.Profile
   err := c.ShouldBindJSON(&user)
   if err != nil {
      c.JSON(http.StatusBadRequest, gin.H{
         "code": 2001,
         "msg":  "无效的参数",
      })
      return
   }
   // 校验用户名和密码是否正确
   _, err := getProfile(user.Username)
   if err != nil {
       c.JSON(http.StatusNotFound, gin.H{
          "code": 2003,
          "msg":  "用户不存在",
       })
       return
   }

   tokenString, _ := Middlewares.GenToken(user.Username)
   c.JSON(http.StatusOK, gin.H{
       "code":     2000,
       "msg":      "success",
       "Token":    tokenString,
       "username": profile.Username,
   })
}

3.解析客户端中带token的请求,通过 authorization字段保存jwt token

// ParseToken 解析JWT
func ParseToken(tokenString string) (*MyClaims, error) {
   // 解析token
   token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) {
      return MySecret, nil
   })
   if err != nil {
      return nil, err
   }
   if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { // 校验token
      return claims, nil
   }
   return nil, errors.New("invalid token")
}

4.定义中间件函数

// JWTAuthMiddleware 基于JWT的认证中间件--验证用户是否登录
func JWTAuthMiddleware() func(c *gin.Context) {
   return func(c *gin.Context) {
      authHeader := c.Request.Header.Get("authorization")
      if authHeader == "" {
         c.JSON(http.StatusUnauthorized, gin.H{
            "code": 2003,
            "msg":  "请求头中auth为空",
         })
         c.Abort()
         return
      }
      // 按空格分割
      parts := strings.Split(authHeader, ".")
      if len(parts) != 3 {
         c.JSON(http.StatusUnauthorized, gin.H{
            "code": 2004,
            "msg":  "请求头中auth格式有误",
         })
         c.Abort()
         return
      }
      mc, ok := ParseToken(authHeader, Secret)
      if ok == false {
         c.JSON(http.StatusUnauthorized, gin.H{
            "code": 2005,
            "msg":  "无效的Token",
         })
         c.Abort()
         return
      }
      m := mc.(jwt.MapClaims)
      // 将当前请求的username信息保存到请求的上下文c上
      c.Set("username", m["username"])
      c.Next() // 后续的处理函数可以用过c.Get("username")来获取当前请求的用户信息
   }
}

拓展

gin框架常用中间件还有SHA1加密,限流中间件等,后续再进行深入学习。