Gin源码分析 – 中间件 – JWT认证机制

Github地址:https://github.com/golang-jwt/jwt

文档地址:https://pkg.go.dev/github.com/golang-jwt/jwt

下载:go get github.com/golang-jwt/jwt

签发token和验证token

package main

import (
  "errors"
  "fmt"
  "github.com/golang-jwt/jwt"
  "time"
)

// 第一步:定义结构体
// MyClaims 定义结构体并继承jwt.StandardClaims
// jwt包自带的jwt.StandardClaims只包含了官方字段
// 我们需要额外记录一个username和id字段,所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type MyCustomClaims struct {
  Id       int    `json:"id"`
  Username string `json:"username"`
  jwt.StandardClaims
}

// 定义加密秘钥
var mySigningKey = []byte("lqzisnb")

func genToken(claims MyCustomClaims) (string, error) {
  // 使用HS256加密方式
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  signToken, err := token.SignedString(mySigningKey)
  if err != nil {
    return "", err
  }
  return signToken, nil
}
func parserToken(signToken string) (*MyCustomClaims, error) {
  var claims MyCustomClaims
  token, err := jwt.ParseWithClaims(signToken, &claims, func(token *jwt.Token) (interface{}, error) {
    return mySigningKey, nil
  })
  if token.Valid {
    return &claims, nil
  } else {
    return nil, err
  }
}
// 带详细错误的解析
func parserTokenWithError(signToken string) (*MyCustomClaims, error) {
  var claims MyCustomClaims
  token, err := jwt.ParseWithClaims(signToken, &claims, func(token *jwt.Token) (interface{}, error) {
    return mySigningKey, nil
  })
  if token.Valid {
    return &claims, nil
  } else if ve, ok := err.(*jwt.ValidationError); ok {
    if ve.Errors&jwt.ValidationErrorMalformed != 0 {
      return nil, errors.New("不是一个合法的token")
    } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
      return nil, errors.New("token过期了")
    } else {
      fmt.Println("Couldn't handle this token:", err)
      return nil, errors.New("无法处理这个token")
    }
  } else {
    return nil, errors.New("无法处理这个token")
  }
}

func main() {
  claims := MyCustomClaims{
    1,
    "lqz",
    jwt.StandardClaims{
      ExpiresAt: time.Now().Add(7 * time.Hour).Unix(), // 过期时间7小时
      Issuer:    "lqz",                                // 签发人
    },
  }
  signToken, _ := genToken(claims)
  fmt.Println(signToken)
  c, err := parserToken(signToken)
  fmt.Println(c, err)
}

Gin框架中集成

package main

import (
  "fmt"
  "github.com/gin-gonic/gin"
  "github.com/golang-jwt/jwt"
  "net/http"
  "time"
)

// 第一步:定义结构体
// MyClaims 定义结构体并继承jwt.StandardClaims
// jwt包自带的jwt.StandardClaims只包含了官方字段
// 我们需要额外记录一个username和id字段,所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type MyCustomClaims struct {
  Id       int    `json:"id"`
  Username string `json:"username"`
  jwt.StandardClaims
}

// 定义加密秘钥
var mySigningKey = []byte("lqzisnb")

func genToken(claims MyCustomClaims) (string, error) {
  // 使用HS256加密方式
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  signToken, err := token.SignedString(mySigningKey)
  if err != nil {
    return "", err
  }
  return signToken, nil
}

func parserToken(signToken string) (*MyCustomClaims, error) {
  var claims MyCustomClaims
  token, err := jwt.ParseWithClaims(signToken, &claims, func(token *jwt.Token) (interface{}, error) {
    return mySigningKey, nil
  })
  if token.Valid {
    return &claims, nil
  } else {
    return nil, err
  }
}

// 基于JWT的认证中间件
func JWTAuthMiddleware(c *gin.Context) {
  // 从请求头中取出
  signToken := c.Request.Header.Get("Authorization")
  if signToken == "" {
    c.JSON(http.StatusOK, gin.H{
      "code": 1002,
      "msg":  "token为空",
    })
    c.Abort()
    return
  }
  // 校验token
  myclaims, err := parserToken(signToken)
  if err != nil {
    fmt.Println(err)
    c.JSON(http.StatusOK, gin.H{
      "code": 1003,
      "msg":  "token校验失败",
    })
    c.Abort()
    return
  }
  // 将用户的id放在到请求的上下文c上
  c.Set("userid", myclaims.Id)
  c.Next() // 后续的处理函数可以用过c.Get("userid")来获取当前请求的id
}

func main() {
  r := gin.Default()
  r.POST("/login", func(c *gin.Context) {

    username := c.PostForm("username")
    password := c.PostForm("password")
    if username == "lqz" && password == "123" {
      token, _ := genToken(MyCustomClaims{1, "lqz", jwt.StandardClaims{
        ExpiresAt: time.Now().Add(7 * time.Hour).Unix(), // 过期时间
        Issuer:    "lqz",                                // 签发人
      }})
      c.JSON(200, gin.H{"code": "100", "msg": "登陆成功", "token": token})
    } else {
      c.JSON(200, gin.H{"code": "101", "msg": "用户名或密码错误"})
    }
  })

  // 该接口登陆后才能访问,加中间件
  r.GET("/home", JWTAuthMiddleware, func(c *gin.Context) {
    fmt.Println(c.Get("userid"))
    c.JSON(200, gin.H{"code": 100, "msg": "home"})
  })
  r.Run(":8080")
}