文章目录



简介

目前主流的 gin 的鉴权框架有 github 上的 dgrijalva/jwt-go 和 golang-jwt/jwt,但是 dgrijalva/jwt-go 以前用的特别多,但是有一些漏洞,现在更推荐的是 golang-jwt/jwt 官方社区版

github:https://github.com/golang-jwt/jwt

官网:https://pkg.go.dev/github.com/golang-jwt/jwt/v4

JWT(JSON Web Tokens),

引入

​go get -u github.com/golang-jwt/jwt​
import "github.com/golang-jwt/jwt"

源码

// A JWT Token.  Different fields will be used depending on whether you're
// creating or parsing/verifying a token.
type Token struct {
Raw string // The raw token. Populated when you Parse a token
Method SigningMethod // The signing method used or to be used
Header map[string]interface{} // The first segment of the token
Claims Claims // The second segment of the token
Signature string // The third segment of the token. Populated when you Parse a token
Valid bool // Is the token valid? Populated when you Parse/Verify a token
}

JWT 中的 token如上图,完整的 token 分为 3 部分


  • The first segment of the token:Header
  • The second segment of the token:Claims
  • The third segment of the token. Populated when you Parse a token:Signature

代码模型


  • 登录的 HandlerFunc 中,需要生成 token,并设置响应头带上这个 token
  • 中间件可以处理分组的请求,检测 Authorization 中数据做解析判定数据是否有效,如果无效直接返回无权限,不会进入到具体 HandlerFunc 中操作,如果有效则把 claims 中的用户信息保存到 gin.context 中,然后放行进入 HandlerFunc 中

router 中写法

r := gin.Default()

authGroup := r.Group("/auth")

// 分组使用 JWT 鉴权中间件
authGroup.Use(middleware.JWTAuth())

middlerware 中写法

中间件中做到解析 token,然后把 token 中的用户信息存放在 gin.Context 中,方便后面 HandlerFunc 中取用

// JWTAuth 鉴权中间件
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 获取请求头中 token,实际是一个完整被签名过的 token;a complete, signed token
tokenStr := c.GetHeader("Authorization")
if tokenStr == "" {
c.JSON(http.StatusForbidden, "No token. You don't have permission!")
c.Abort()
return
}

// 解析拿到完整有效的 token,里头包含解析后的 3 segment
token, err := ParseToken(tokenStr)
if err != nil {
c.JSON(http.StatusForbidden, "Invalid token! You don't have permission!")
c.Abort()
return
}
// 获取 token 中的 claims
claims, ok := token.Claims.(*AuthClaims)
if !ok {
c.JSON(http.StatusForbidden, "Invalid token!")
c.Abort()
return
}

// 将 claims 中的用户信息存储在 context 中
c.Set("userId", claims.UserId)

// 这里执行路由 HandlerFunc
c.Next()
}
}

ParseToken 解析 token string => *jwt.Token

// ParseToken 解析请求头中的 token string,转换成被解析后的 jwt.Token
func ParseToken(tokenStr string) (*jwt.Token, error) {
// 解析 token string 拿到 token jwt.Token
return jwt.ParseWithClaims(tokenStr, &AuthClaims{}, func(tk *jwt.Token) (interface{}, error) {
return secretKey, nil
})
}

secretKey 密钥

建议不要存放在项目代码中,要单独私有存放

var secretKey = []byte("some string")

AuthClaims 结构体

// AuthClaims 是 claims struct
type AuthClaims struct {
UserId uint64 `json:"userId"`
jwt.StandardClaims
}

GenerateToken 生成 *jwt.Token

一般在 login 后来生成 *jwt.Token 存放在响应头中给浏览器

// GenerateToken 一般在登录之后使用来生成 token 能够返回给前端
func GenerateToken(userId uint64, expireTime time.Time) (string, error) {
// 创建一个 claim
claim := AuthClaims{
UserId: userId,
StandardClaims: jwt.StandardClaims{
// 过期时间
ExpiresAt: expireTime.Unix(),
// 签名时间
IssuedAt: time.Now().Unix(),
// 签名颁发者
Issuer: "abcnull",
// 签名主题
Subject: "gindemo",
},
}

// 使用指定的签名加密方式创建 token,有 1,2 段内容,第 3 段内容没有加上
noSignedToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)

// 使用 secretKey 密钥进行加密处理后拿到最终 token string
return noSignedToken.SignedString(secretKey)
}

login 登录后响应头设置 cookie

login 登录之后需要把 token string 带到响应头中返回给前端浏览器来保存信息

// 查询数据库,通过用户密码拿到 userId
userId := 1

// token 过期时间 60 天,Time 类型
var expireTime = time.Now().Add(60 * 24 * time.Hour)

// 生成 token string
tokenStr, tokenErr := middleware.GenerateToken(userId, expireTime)
if tokenErr != nil {
// todo: 做一些出错处理
}

// 设置响应头信息的 token
c.SetCookie("Authorization", tokenStr, 60, "/", "127.0.0.1", false, true)

常规 HandlerFunc 使用用户信息

// 登录之后可以接入 Get 拿到用户信息,返回是 (interface{}, bool)
userId, flag := c.Get("userId")

// todo: 通过 userId 可以查询数据库获取想要的用户信息