package middleware import ( "sync" "time" "github.com/golang-jwt/jwt/v5" ) // tokenBlacklist 登出令牌黑名单(内存版,进程重启后失效,令牌自然过期兜底) type tokenBlacklist struct { mu sync.RWMutex revoked map[string]time.Time // tokenID(jti) -> 过期时间 } var defaultBlacklist = &tokenBlacklist{revoked: make(map[string]time.Time)} // RevokeToken 将令牌加入黑名单(按 jti,若无 jti 则按 subject+签发时间) func RevokeToken(claims *JWTClaims, tokenStr string, exp time.Time) { id := tokenIdentifier(claims) defaultBlacklist.mu.Lock() defaultBlacklist.revoked[id] = exp defaultBlacklist.mu.Unlock() } // IsRevoked 判断令牌是否已被吊销 func IsRevoked(claims *JWTClaims) bool { id := tokenIdentifier(claims) defaultBlacklist.mu.RLock() exp, ok := defaultBlacklist.revoked[id] defaultBlacklist.mu.RUnlock() if !ok { return false } // 已过期的黑名单项自动清理 if time.Now().After(exp) { defaultBlacklist.mu.Lock() delete(defaultBlacklist.revoked, id) defaultBlacklist.mu.Unlock() return false } return true } // ExtractClaims 从 token 字符串解析 claims(供 logout handler 使用) func ExtractClaims(tokenStr string, secret string) (*JWTClaims, *jwt.Token, error) { claims := &JWTClaims{} token, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) { return []byte(secret), nil }, jwt.WithValidMethods([]string{"HS256"})) return claims, token, err } // tokenIdentifier 返回令牌唯一标识:优先 jti,否则用 subject+签发时间 func tokenIdentifier(claims *JWTClaims) string { if claims.ID != "" { return claims.ID } iat := "" if claims.IssuedAt != nil { iat = claims.IssuedAt.Format(time.RFC3339Nano) } return claims.Subject + "|" + iat }