如何在Go中使用JWT

4,487 阅读3分钟

为什么使用JWT

在过去的web项目中,通常使用的是Cookie-Session的认证方式。大致的流程如下:

  1. 用户使用用户名个密码登录,发送信息到服务器。
  2. 服务器拿到用户的信息,后会生成一份保存用户信息的session数据和与之对应的cookie id,然后保存session在在服务器,把对应的session id返回给用户并保存在浏览器。
  3. 后续来自该用户的每次请求都会携带相应的cookie。
  4. 服务端通过带来的cookie找到之前保存的与之对应的用户信息。

那么这种方式有什么弊端呢?

  • 不支持跨域
  • 通过数据库查询响应的用户信息耗时
  • CSRF攻击(跨站伪造请求)

什么是JWT

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

在gin框架中使用JWT

  1. 需要提前安装的包:
github.com/dgrijalva/jwt-go

github.com/gin-gonic/gin
  1. 生成token
type MyClaims struct {
   Username string `json:"username"`
   jwt.StandardClaims
}

// 定义过期时间
const TokenExpireDuration = time.Hour * 2

//定义secret
var MySecret = []byte("这是一段用于生成token的密钥")

//生成jwt
func GenToken(username string) (string, error) {
   c := MyClaims{
      username,
      jwt.StandardClaims{
         ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
         Issuer:    "my-project",
      },
   }
   //使用指定的签名方法创建签名对象
   token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)

   //使用指定的secret签名并获得完成的编码后的字符串token
   return token.SignedString(MySecret)
}
  1. 解析JWT

//解析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 {
      return claims, nil
   }
   return nil, errors.New("invalid token")
}
  1. 编写基于JWT认证中间件

//基于JWT认证中间件
func JWTAuthMiddleware() func(c *gin.Context) {
   return func(c *gin.Context) {
      authHeader := c.Request.Header.Get("Authorization")
      if authHeader == "" {
         c.JSON(http.StatusOK, gin.H{
            "code": 2003,
            "msg":  "请求头中的auth为空",
         })
         c.Abort()
         return
      }
      parts := strings.SplitN(authHeader, " ", 2)

      if !(len(parts) == 2 && parts[0] == "Bearer") {
         c.JSON(http.StatusOK, gin.H{
            "code": 2004,
            "msg":  "请求头中的auth格式错误",
         })
         //阻止调用后续的函数
         c.Abort()
         return
      }
      mc, err := ParseToken(parts[1])
      if err != nil {
         c.JSON(http.StatusOK, gin.H{
            "code": 2005,
            "msg":  "无效的token",
         })
         c.Abort()
         return
      }
      //将当前请求的username信息保存到请求的上下文c上
      c.Set("username", mc.Username)
      //后续的处理函数可以通过c.Get("username")来获取请求的用户信息
      c.Next()
   }

}

5 . 注册一条理由/auth,获取token


type UserInfo struct {
   Username string `json:"username"`
   Password string `json:"password"`
}

func authHandler(c *gin.Context) {
   var user UserInfo
   err := c.ShouldBind(&user)
   if err != nil {
      c.JSON(http.StatusOK, gin.H{
         "code": 2001,
         "msg":  "无效的参数",
      })
      return
   }

   if user.Username == "cyl" && user.Password == "123456" {
      //生成token
      tokenString, _ := GenToken(user.Username)
      c.JSON(http.StatusOK, gin.H{
         "code": 200,
         "msg":  "success",
         "data": gin.H{"token": tokenString},
      })
      return
   }

   c.JSON(http.StatusOK, gin.H{
      "code": 2002,
      "msg":  "鉴权失败",
   })
   return
}
  1. 编写main函数进行测试

package main

import (
   "errors"
   "github.com/dgrijalva/jwt-go"
   "github.com/gin-gonic/gin"
   "net/http"
   "strings"
   "time"
)

func main() {
   r := gin.Default()
   r.POST("/auth", authHandler)  
   r.GET("/home", JWTAuthMiddleware(), homeHandler)
   r.Run(":9000")
}
  1. 使用postman进行测试 首先访问一下http://localhost:9000/auth 返回结果如下:

微信截图_20220502151940.png

接着我们继续注册一个需要token的路由/home

func homeHandler(c *gin.Context) {
   username := c.MustGet("username").(string)
   c.JSON(http.StatusOK, gin.H{
      "code": 2000,
      "msg":  "success",
      "data": gin.H{"username": username},
   })
}

访问http://localhost:9000/home 并且带上上次获取的token(记得加上Bearer)

结果:

微信截图_20220502152201.png

go中使用JWT就到此结束啦,附上原文连接:www.liwenzhou.com/posts/Go/jw…