114 lines
3.1 KiB
Go
114 lines
3.1 KiB
Go
package middleware
|
||
|
||
import (
|
||
"errors"
|
||
"strconv"
|
||
"time"
|
||
|
||
"kra/pkg/jwt"
|
||
"kra/pkg/response"
|
||
"kra/pkg/utils"
|
||
|
||
jwtv5 "github.com/golang-jwt/jwt/v5"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// BlacklistChecker JWT黑名单检查接口
|
||
type BlacklistChecker interface {
|
||
IsBlacklist(jwt string) bool
|
||
}
|
||
|
||
// JWTConfig JWT中间件配置
|
||
type JWTConfig struct {
|
||
JWT *jwt.JWT
|
||
BlacklistChecker BlacklistChecker
|
||
UseMultipoint bool
|
||
SetRedisJWT func(token, username string) error
|
||
}
|
||
|
||
// 全局配置(需要在初始化时设置)
|
||
var jwtConfig *JWTConfig
|
||
|
||
// SetJWTConfig 设置JWT配置
|
||
func SetJWTConfig(cfg *JWTConfig) {
|
||
jwtConfig = cfg
|
||
// 同时设置 utils 包的 JWT 实例
|
||
if cfg != nil && cfg.JWT != nil {
|
||
utils.SetJWT(cfg.JWT)
|
||
}
|
||
}
|
||
|
||
// JWTAuth JWT认证中间件(与 kra 保持一致)
|
||
func JWTAuth() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
|
||
token := utils.GetToken(c)
|
||
if token == "" {
|
||
response.NoAuth("未登录或非法访问,请登录", c)
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
// 检查黑名单
|
||
if jwtConfig != nil && jwtConfig.BlacklistChecker != nil && jwtConfig.BlacklistChecker.IsBlacklist(token) {
|
||
response.NoAuth("您的帐户异地登陆或令牌失效", c)
|
||
utils.ClearToken(c)
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
if jwtConfig == nil || jwtConfig.JWT == nil {
|
||
response.NoAuth("JWT配置错误", c)
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
// parseToken 解析token包含的信息
|
||
claims, err := jwtConfig.JWT.ParseToken(token)
|
||
if err != nil {
|
||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||
response.NoAuth("登录已过期,请重新登录", c)
|
||
utils.ClearToken(c)
|
||
c.Abort()
|
||
return
|
||
}
|
||
response.NoAuth(err.Error(), c)
|
||
utils.ClearToken(c)
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
// 已登录用户被管理员禁用 需要使该用户的jwt失效 此处比较消耗性能 如果需要 请自行打开
|
||
// 用户被删除的逻辑 需要优化 此处比较消耗性能 如果需要 请自行打开
|
||
|
||
c.Set("claims", claims)
|
||
|
||
// 检查是否需要刷新token
|
||
if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime {
|
||
claims.ExpiresAt = jwtv5.NewNumericDate(time.Now().Add(jwtConfig.JWT.ExpiresAt))
|
||
newToken, newClaims, err := jwtConfig.JWT.CreateTokenByOldToken(token, *claims)
|
||
if err == nil {
|
||
c.Header("new-token", newToken)
|
||
c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10))
|
||
utils.SetToken(c, newToken, int(jwtConfig.JWT.ExpiresAt.Seconds()/60))
|
||
|
||
// 多点登录记录新JWT
|
||
if jwtConfig.UseMultipoint && jwtConfig.SetRedisJWT != nil {
|
||
_ = jwtConfig.SetRedisJWT(newToken, newClaims.Username)
|
||
}
|
||
}
|
||
}
|
||
|
||
c.Next()
|
||
|
||
// 处理后续中间件设置的新token
|
||
if newToken, exists := c.Get("new-token"); exists {
|
||
c.Header("new-token", newToken.(string))
|
||
}
|
||
if newExpiresAt, exists := c.Get("new-expires-at"); exists {
|
||
c.Header("new-expires-at", newExpiresAt.(string))
|
||
}
|
||
}
|
||
}
|