527 lines
18 KiB
Go
527 lines
18 KiB
Go
package jwt
|
||
|
||
import (
|
||
"context"
|
||
"crypto/rand"
|
||
"encoding/base64"
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
"sync"
|
||
"sync/atomic"
|
||
"time"
|
||
|
||
"github.com/EthanCodeCraft/xlgo-core/config"
|
||
"github.com/EthanCodeCraft/xlgo-core/database"
|
||
"github.com/EthanCodeCraft/xlgo-core/logger"
|
||
"github.com/golang-jwt/jwt/v5"
|
||
"github.com/redis/go-redis/v9"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// Claims JWT 声明
|
||
type Claims struct {
|
||
UserID uint `json:"user_id"`
|
||
Username string `json:"username"`
|
||
Role string `json:"role"` // admin 或 staff
|
||
UserType string `json:"user_type"` // super_admin, admin, staff
|
||
JTI string `json:"jti"` // JWT ID(唯一标识,用于黑名单)
|
||
jwt.RegisteredClaims
|
||
}
|
||
|
||
var (
|
||
//ErrTokenExpired 令牌已过期
|
||
ErrTokenExpired = errors.New("令牌已过期")
|
||
//ErrTokenInvalid 令牌无效
|
||
ErrTokenInvalid = errors.New("令牌无效")
|
||
//ErrTokenMalformed 令牌格式错误
|
||
ErrTokenMalformed = errors.New("令牌格式错误")
|
||
//ErrTokenNotValidYet 令牌尚未生效
|
||
ErrTokenNotValidYet = errors.New("令牌尚未生效")
|
||
//ErrTokenRevoked 令牌已被撤销
|
||
ErrTokenRevoked = errors.New("令牌已被撤销")
|
||
// ErrBlacklistUnavailable Redis 未初始化或不可用,黑名单功能失效(C9a 修复)。
|
||
// Add 返回此错误使调用方(RefreshToken/InvalidateToken)感知黑名单不可用并 fail-closed,
|
||
// 避免无 Redis 时静默成功致撤销/刷新失效、新旧 token 双有效。
|
||
// IsBlacklisted 在无 Redis 时仍返回 false(验证侧 fail-open 是无 Redis 部署的固有局限,
|
||
// 文档约束:安全敏感场景必须启用 Redis)。
|
||
ErrBlacklistUnavailable = errors.New("token 黑名单不可用:Redis 未初始化")
|
||
// ErrEmptySecret JWT 密钥为空(P0 修复)。空 secret 意味着以零长度 HMAC 密钥签发/校验,
|
||
// 任何以 "" 签名的 token 都会通过——签发与校验一律 fail-closed 拒绝,杜绝该空密钥绕过。
|
||
ErrEmptySecret = errors.New("jwt.secret 未配置:拒绝签发/校验(防空密钥导致任意 token 通过)")
|
||
// ErrUnsupportedAlgorithm 配置了不支持的签名算法(P0 修复)。
|
||
// 本实现仅支持 HMAC 族(HS256/HS384/HS512);RS256 等非对称算法暂不支持,
|
||
// 不再静默回退 HS256——避免用户误以为在用非对称算法、实则 HMAC,并助长算法混淆攻击。
|
||
ErrUnsupportedAlgorithm = errors.New("jwt: 不支持的签名算法(仅支持 HS256/HS384/HS512)")
|
||
// ErrInvalidExpiry 过期时间非法。签发非正过期时间的 token 会立即失效或产生不可预期会话语义。
|
||
ErrInvalidExpiry = errors.New("jwt: 过期时间必须大于 0")
|
||
// ErrEmptyJTI 空 JTI 无法匹配任何正常 token,却会写入 jwt_bl: 这种永不命中的黑名单键。
|
||
ErrEmptyJTI = errors.New("jwt: jti 不能为空")
|
||
)
|
||
|
||
// validMethods 允许的签名算法名(HMAC 族)。ParseWithClaims 传 jwt.WithValidMethods 固定算法,
|
||
// 防算法混淆(alg confusion,P0):拒绝 alg=none 及非 HMAC 算法——否则若部署配置为非对称算法,
|
||
// 攻击者可用公钥作为 HMAC 密钥伪造 token 通过校验。
|
||
var validMethods = []string{"HS256", "HS384", "HS512"}
|
||
|
||
// secretKey 返回 HMAC 密钥字节;cfg 为 nil 或密钥为空时 fail-closed 返回 ErrEmptySecret(P0)。
|
||
func secretKey(cfg *config.Config) ([]byte, error) {
|
||
if cfg == nil || cfg.JWT.Secret == "" {
|
||
return nil, ErrEmptySecret
|
||
}
|
||
return []byte(cfg.JWT.Secret), nil
|
||
}
|
||
|
||
// hmacKeyfunc 构造校验签名方法为 HMAC 族并返回密钥的 jwt.Keyfunc(P0:防算法混淆 + 空密钥)。
|
||
// 双重防护:① 断言 token.Method 为 *jwt.SigningMethodHMAC,拒绝非 HMAC(含 none/RS/ES);
|
||
// ② 经 secretKey 拒绝空密钥。配合 ParseWithClaims 的 jwt.WithValidMethods(validMethods) 使用。
|
||
func hmacKeyfunc(cfg *config.Config) jwt.Keyfunc {
|
||
return func(token *jwt.Token) (any, error) {
|
||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||
return nil, fmt.Errorf("%w: 期望 HMAC,实际 alg=%v", ErrUnsupportedAlgorithm, token.Header["alg"])
|
||
}
|
||
return secretKey(cfg)
|
||
}
|
||
}
|
||
|
||
// generateJTI 生成唯一的 JWT ID
|
||
func generateJTI() (string, error) {
|
||
bytes := make([]byte, 16)
|
||
if _, err := rand.Read(bytes); err != nil {
|
||
return "", fmt.Errorf("生成 JTI 失败: %w", err)
|
||
}
|
||
return base64.URLEncoding.EncodeToString(bytes), nil
|
||
}
|
||
|
||
// TokenBlacklist Token 黑名单管理(使用 JTI 优化)。
|
||
// client 为 nil 时回退到 database.GetRedis(),兼容存量未注入场景。
|
||
type TokenBlacklist struct {
|
||
client *redis.Client
|
||
}
|
||
|
||
// NewTokenBlacklist 创建黑名单实例,client 可为 nil(懒取全局 Redis)。
|
||
func NewTokenBlacklist(client *redis.Client) *TokenBlacklist {
|
||
return &TokenBlacklist{client: client}
|
||
}
|
||
|
||
func (tb *TokenBlacklist) redisClient() *redis.Client {
|
||
if tb != nil && tb.client != nil {
|
||
return tb.client
|
||
}
|
||
return database.GetRedis()
|
||
}
|
||
|
||
// blacklistOpTimeout 黑名单 Redis 操作的上下文超时(M-A 修复)。
|
||
// Redis 客户端已配 ReadTimeout/WriteTimeout=3s(redis.go D7),但鉴权热路径(ParseToken
|
||
// 每次调 IsBlacklisted)需更紧边界——显式 ctx 超时把鉴权阻塞上限收敛到 1s,避免 Redis
|
||
// 挂起时每个鉴权请求被长时间拖住。健康 Redis 下 Exists/SET 为亚毫秒级,1s 余量充足。
|
||
// 注:ctx 超时只约束命令往返,不影响 Set 的服务端 TTL(ttl 可远大于 1s)。
|
||
const blacklistOpTimeout = 1 * time.Second
|
||
|
||
// BlacklistPolicy 控制解析 Token 时遇到黑名单查询错误的处理策略。
|
||
// ParseToken 默认 BlacklistFailClosed(黑名单不可检查即拒绝);
|
||
// 需显式 fail-open(仅无 Redis 或低安全场景)用 ParseTokenWithBlacklistPolicy(..., BlacklistFailOpen)。
|
||
type BlacklistPolicy int
|
||
|
||
const (
|
||
// BlacklistFailOpen 黑名单查询出错时放行 Token(仅用于无 Redis 或低安全场景,非默认)。
|
||
BlacklistFailOpen BlacklistPolicy = iota
|
||
// BlacklistFailClosed 黑名单查询出错时拒绝 Token(默认;ParseToken 即用此策略)。
|
||
BlacklistFailClosed
|
||
)
|
||
|
||
// blacklistCtx 创建带超时的 context 用于黑名单 Redis 操作(M-A)。
|
||
func blacklistCtx() (context.Context, context.CancelFunc) {
|
||
return context.WithTimeout(context.Background(), blacklistOpTimeout)
|
||
}
|
||
|
||
// Add 将 Token 的 JTI 加入黑名单
|
||
// 参数: jti JWT ID,expiry Token 过期时间
|
||
//
|
||
// 无 Redis 时返回 ErrBlacklistUnavailable(C9a 修复):让调用方(RefreshToken/InvalidateToken)
|
||
// 感知黑名单不可用并 fail-closed,避免无 Redis 时静默成功致撤销失效。
|
||
func (tb *TokenBlacklist) Add(jti string, expiry time.Time) error {
|
||
if strings.TrimSpace(jti) == "" {
|
||
return ErrEmptyJTI
|
||
}
|
||
|
||
client := tb.redisClient()
|
||
if client == nil {
|
||
// Redis 未启用,黑名单不可用——fail-closed 让调用方决策。
|
||
return ErrBlacklistUnavailable
|
||
}
|
||
|
||
ctx, cancel := blacklistCtx()
|
||
defer cancel()
|
||
ttl := time.Until(expiry)
|
||
if ttl <= 0 {
|
||
// Token 已过期,无需加入黑名单
|
||
return nil
|
||
}
|
||
|
||
// 使用 JTI 作为键名(约24字节),而非完整 Token(数百字节)
|
||
key := fmt.Sprintf("jwt_bl:%s", jti)
|
||
return client.Set(ctx, key, "1", ttl).Err()
|
||
}
|
||
|
||
// IsBlacklisted 检查 JTI 是否在黑名单中。
|
||
// 命中返回 (true, nil);未命中返回 (false, nil);
|
||
// Redis 未启用或不可达返回 (false, ErrBlacklistUnavailable),由调用方决定 fail-open/fail-closed。
|
||
func (tb *TokenBlacklist) IsBlacklisted(jti string) (bool, error) {
|
||
client := tb.redisClient()
|
||
if client == nil {
|
||
return false, ErrBlacklistUnavailable
|
||
}
|
||
|
||
ctx, cancel := blacklistCtx()
|
||
defer cancel()
|
||
key := fmt.Sprintf("jwt_bl:%s", jti)
|
||
n, err := client.Exists(ctx, key).Result()
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
return n > 0, nil
|
||
}
|
||
|
||
// Manager JWT 管理器(#10)。持有独立的 TokenBlacklist,
|
||
// 支持多实例(如区分 user-token 与 refresh-token 黑名单)。
|
||
type Manager struct {
|
||
mu sync.Mutex
|
||
blacklist *TokenBlacklist
|
||
}
|
||
|
||
// defaultManager 是全局默认 JWT 管理器的真实存储,经 atomic 读写(C9c)。
|
||
var defaultManager atomic.Pointer[Manager]
|
||
|
||
func init() {
|
||
defaultManager.Store(NewJWTManager())
|
||
}
|
||
|
||
// currentManager 返回全局默认 JWT 管理器(atomic 读取,C9c)。
|
||
// 正常情况下 init 后永不为 nil;防御性地在极罕见的 nil 情况下回退一个懒取 Redis 的实例。
|
||
func currentManager() *Manager {
|
||
if m := defaultManager.Load(); m != nil {
|
||
return m
|
||
}
|
||
m := NewJWTManager()
|
||
defaultManager.Store(m)
|
||
return m
|
||
}
|
||
|
||
// GetDefaultJWT 返回全局默认 JWT 管理器(并发安全,C9c/J1 修复)。
|
||
// 替代已删除的 DefaultJWT 包级变量。
|
||
func GetDefaultJWT() *Manager {
|
||
return currentManager()
|
||
}
|
||
|
||
// currentBlacklist 返回全局默认 Manager 持有的黑名单(atomic 读取 Manager 后经 Blacklist(),C9c)。
|
||
func currentBlacklist() *TokenBlacklist {
|
||
return currentManager().Blacklist()
|
||
}
|
||
|
||
// NewJWTManager 创建 JWT 管理器实例(blacklist 懒取全局 Redis)。
|
||
func NewJWTManager() *Manager {
|
||
return &Manager{blacklist: NewTokenBlacklist(nil)}
|
||
}
|
||
|
||
// NewJWTManagerWithRedis 创建 JWT 管理器并注入指定 Redis 客户端(用于多 Redis/测试隔离)。
|
||
func NewJWTManagerWithRedis(client *redis.Client) *Manager {
|
||
return &Manager{blacklist: NewTokenBlacklist(client)}
|
||
}
|
||
|
||
// SetDefaultJWTManager 提升指定 Manager 为全局默认(atomic 置换,并发安全,J1 修复)。
|
||
func SetDefaultJWTManager(m *Manager) {
|
||
if m == nil {
|
||
return
|
||
}
|
||
defaultManager.Store(m)
|
||
}
|
||
|
||
// Blacklist 返回 Manager 持有的黑名单实例。
|
||
func (m *Manager) Blacklist() *TokenBlacklist {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
return m.blacklist
|
||
}
|
||
|
||
// GenerateToken 生成 JWT Token
|
||
func GenerateToken(userID uint, username, role, userType string) (string, error) {
|
||
cfg := config.Get()
|
||
var expiry time.Duration
|
||
if cfg != nil {
|
||
expiry = cfg.JWT.Expire
|
||
}
|
||
return generateTokenWithExpiry(cfg, userID, username, role, userType, expiry)
|
||
}
|
||
|
||
func generateTokenWithExpiry(cfg *config.Config, userID uint, username, role, userType string, expiry time.Duration) (string, error) {
|
||
if expiry <= 0 {
|
||
return "", ErrInvalidExpiry
|
||
}
|
||
// P0:先校验密钥非空与算法受支持(fail-closed)。secretKey 亦守卫 cfg==nil,
|
||
// 通过后 cfg 保证非空,后续访问 cfg.JWT.* 安全。
|
||
key, err := secretKey(cfg)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
method, err := signingMethod(cfg.JWT.Algorithm)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 生成唯一的 JWT ID
|
||
jti, err := generateJTI()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
claims := Claims{
|
||
UserID: userID,
|
||
Username: username,
|
||
Role: role,
|
||
UserType: userType,
|
||
JTI: jti,
|
||
RegisteredClaims: jwt.RegisteredClaims{
|
||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiry)),
|
||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||
Issuer: issuerOrDefault(cfg.JWT.Issuer),
|
||
ID: jti, // 同时设置到 RegisteredClaims.ID
|
||
},
|
||
}
|
||
|
||
token := jwt.NewWithClaims(method, claims)
|
||
return token.SignedString(key)
|
||
}
|
||
|
||
// GenerateTokenWithCustomExpiry 生成带自定义过期时间的 Token
|
||
func GenerateTokenWithCustomExpiry(userID uint, username, role, userType string, expireSeconds int) (string, error) {
|
||
cfg := config.Get()
|
||
if expireSeconds <= 0 {
|
||
return "", ErrInvalidExpiry
|
||
}
|
||
return generateTokenWithExpiry(cfg, userID, username, role, userType, time.Duration(expireSeconds)*time.Second)
|
||
}
|
||
|
||
// issuerOrDefault 返回配置的 issuer,未配置时回退 "xlgo"。
|
||
func issuerOrDefault(issuer string) string {
|
||
if issuer == "" {
|
||
return "xlgo"
|
||
}
|
||
return issuer
|
||
}
|
||
|
||
// signingMethod 根据 algorithm 配置返回 HMAC 签名方法。
|
||
// 支持 HS256(默认,空值等价)/HS384/HS512;其它值(含 RS256 等非对称算法,暂不支持)
|
||
// 返回 ErrUnsupportedAlgorithm,不再静默回退 HS256(P0:防"配 RS256 实得 HMAC"的算法混淆隐患)。
|
||
func signingMethod(algorithm string) (jwt.SigningMethod, error) {
|
||
switch strings.ToUpper(strings.TrimSpace(algorithm)) {
|
||
case "", "HS256":
|
||
return jwt.SigningMethodHS256, nil
|
||
case "HS384":
|
||
return jwt.SigningMethodHS384, nil
|
||
case "HS512":
|
||
return jwt.SigningMethodHS512, nil
|
||
default:
|
||
return nil, fmt.Errorf("%w: %q", ErrUnsupportedAlgorithm, algorithm)
|
||
}
|
||
}
|
||
|
||
func parseOptions(cfg *config.Config) []jwt.ParserOption {
|
||
issuer := "xlgo"
|
||
if cfg != nil {
|
||
issuer = issuerOrDefault(cfg.JWT.Issuer)
|
||
}
|
||
return []jwt.ParserOption{
|
||
jwt.WithValidMethods(validMethods),
|
||
jwt.WithIssuer(issuer),
|
||
}
|
||
}
|
||
|
||
func validateIssuer(cfg *config.Config, claims *Claims) error {
|
||
want := "xlgo"
|
||
if cfg != nil {
|
||
want = issuerOrDefault(cfg.JWT.Issuer)
|
||
}
|
||
if claims == nil || claims.Issuer != want {
|
||
return ErrTokenInvalid
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func mapParseTokenError(err error) error {
|
||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||
return ErrTokenExpired
|
||
}
|
||
if errors.Is(err, jwt.ErrTokenMalformed) {
|
||
return ErrTokenMalformed
|
||
}
|
||
if errors.Is(err, jwt.ErrTokenNotValidYet) {
|
||
return ErrTokenNotValidYet
|
||
}
|
||
if errors.Is(err, ErrEmptySecret) {
|
||
return ErrEmptySecret
|
||
}
|
||
if errors.Is(err, ErrUnsupportedAlgorithm) {
|
||
return ErrUnsupportedAlgorithm
|
||
}
|
||
return fmt.Errorf("%w: %w", ErrTokenInvalid, err)
|
||
}
|
||
|
||
func checkTokenBlacklist(claims *Claims, policy BlacklistPolicy) error {
|
||
if claims == nil || claims.JTI == "" {
|
||
return nil
|
||
}
|
||
revoked, err := currentBlacklist().IsBlacklisted(claims.JTI)
|
||
if err != nil {
|
||
if policy == BlacklistFailClosed {
|
||
return err
|
||
}
|
||
logger.Warn("JWT 黑名单检查失败,fail-open 策略放行 token", zap.String("jti", claims.JTI), zap.Error(err))
|
||
return nil
|
||
}
|
||
if revoked {
|
||
return ErrTokenRevoked
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ParseToken 解析 JWT Token。默认 fail-closed:黑名单后端不可检查时返回
|
||
// ErrBlacklistUnavailable,拒绝该 Token。需要显式 fail-open(仅无 Redis 或低安全场景)
|
||
// 请用 ParseTokenWithBlacklistPolicy(token, BlacklistFailOpen)。
|
||
func ParseToken(tokenString string) (*Claims, error) {
|
||
return ParseTokenWithBlacklistPolicy(tokenString, BlacklistFailClosed)
|
||
}
|
||
|
||
// ParseTokenWithBlacklistPolicy 使用显式黑名单查询策略解析 JWT Token。
|
||
func ParseTokenWithBlacklistPolicy(tokenString string, policy BlacklistPolicy) (*Claims, error) {
|
||
cfg := config.Get()
|
||
|
||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, hmacKeyfunc(cfg), parseOptions(cfg)...)
|
||
|
||
if err != nil {
|
||
return nil, mapParseTokenError(err)
|
||
}
|
||
|
||
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
||
// 使用 JTI 检查黑名单(更高效)
|
||
if err := checkTokenBlacklist(claims, policy); err != nil {
|
||
return nil, err
|
||
}
|
||
return claims, nil
|
||
}
|
||
|
||
return nil, ErrTokenInvalid
|
||
}
|
||
|
||
// InvalidateToken 使 Token 失效(加入黑名单)
|
||
func InvalidateToken(tokenString string) error {
|
||
cfg := config.Get()
|
||
|
||
opts := append(parseOptions(cfg), jwt.WithoutClaimsValidation())
|
||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, hmacKeyfunc(cfg), opts...)
|
||
|
||
if err != nil {
|
||
// Token 签名/格式/issuer 无效,无需加入黑名单。
|
||
return nil
|
||
}
|
||
|
||
if claims, ok := token.Claims.(*Claims); ok {
|
||
if err := validateIssuer(cfg, claims); err != nil {
|
||
return nil
|
||
}
|
||
if strings.TrimSpace(claims.JTI) == "" {
|
||
return ErrEmptyJTI
|
||
}
|
||
if claims.ExpiresAt != nil {
|
||
return currentBlacklist().Add(claims.JTI, claims.ExpiresAt.Time)
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// InvalidateTokenByID 直接通过 JTI 使 Token 失效
|
||
// 参数: jti JWT ID,expiry 过期时间
|
||
func InvalidateTokenByID(jti string, expiry time.Time) error {
|
||
if strings.TrimSpace(jti) == "" {
|
||
return ErrEmptyJTI
|
||
}
|
||
return currentBlacklist().Add(jti, expiry)
|
||
}
|
||
|
||
// RefreshToken 刷新 Token
|
||
//
|
||
// 安全约束(C9b 修复):将旧 Token 加入黑名单的 Add 错误必须向上传播——若 Add 失败
|
||
// (Redis 抖动或未启用)仍签发新 token,会导致旧 token 未拉黑、新旧 token 双有效,
|
||
// 形成会话固定窗口。故 Add 失败时不签发新 token(fail-closed)。
|
||
func RefreshToken(tokenString string) (string, error) {
|
||
claims, err := ParseToken(tokenString)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 将旧 Token 加入黑名单;失败则不签发新 token(C9b:禁止吞 Add 错误)。
|
||
if strings.TrimSpace(claims.JTI) == "" {
|
||
return "", ErrEmptyJTI
|
||
}
|
||
if claims.ExpiresAt != nil {
|
||
if err := currentBlacklist().Add(claims.JTI, claims.ExpiresAt.Time); err != nil {
|
||
return "", fmt.Errorf("刷新令牌失败:旧令牌撤销失败: %w", err)
|
||
}
|
||
}
|
||
|
||
cfg := config.Get()
|
||
var expiry time.Duration
|
||
if cfg != nil {
|
||
expiry = cfg.JWT.RefreshExpire
|
||
}
|
||
if expiry <= 0 && cfg != nil {
|
||
expiry = cfg.JWT.Expire
|
||
}
|
||
return generateTokenWithExpiry(cfg, claims.UserID, claims.Username, claims.Role, claims.UserType, expiry)
|
||
}
|
||
|
||
// GetJTI 从 Token 中提取 JTI(不验证签名)
|
||
// 用于需要在验证前获取 JTI 的场景
|
||
func GetJTI(tokenString string) (string, error) {
|
||
token, _, err := jwt.NewParser().ParseUnverified(tokenString, &Claims{})
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
if claims, ok := token.Claims.(*Claims); ok {
|
||
return claims.JTI, nil
|
||
}
|
||
|
||
return "", ErrTokenInvalid
|
||
}
|
||
|
||
// IsTokenRevoked 检查 Token 是否被撤销(通过 JTI)。
|
||
// 返回 (是否撤销, 错误);黑名单后端不可用时返回 (false, ErrBlacklistUnavailable)。
|
||
func IsTokenRevoked(jti string) (bool, error) {
|
||
return currentBlacklist().IsBlacklisted(jti)
|
||
}
|
||
|
||
// GetClaimsFromToken 获取 Token 的 Claims(不验证过期)
|
||
// 用于获取已过期 Token 的信息
|
||
func GetClaimsFromToken(tokenString string) (*Claims, error) {
|
||
cfg := config.Get()
|
||
|
||
opts := append(parseOptions(cfg), jwt.WithoutClaimsValidation())
|
||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, hmacKeyfunc(cfg), opts...)
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if claims, ok := token.Claims.(*Claims); ok {
|
||
if err := validateIssuer(cfg, claims); err != nil {
|
||
return nil, err
|
||
}
|
||
return claims, nil
|
||
}
|
||
|
||
return nil, ErrTokenInvalid
|
||
}
|