From ee7e47a8629182b99c7937ccadf5bd47a6341e54 Mon Sep 17 00:00:00 2001 From: zerosaturation Date: Thu, 14 May 2026 16:07:20 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=20JWT=20Token=20?= =?UTF-8?q?=E9=BB=91=E5=90=8D=E5=8D=95=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 设计 Redis Token 黑名单功能,用于账号封禁和强制下线场景。 Co-Authored-By: Claude Opus 4.6 --- ...2026-05-14-redis-token-blacklist-design.md | 61 +++++++------------ 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/docs/superpowers/specs/2026-05-14-redis-token-blacklist-design.md b/docs/superpowers/specs/2026-05-14-redis-token-blacklist-design.md index 1112214..780a628 100644 --- a/docs/superpowers/specs/2026-05-14-redis-token-blacklist-design.md +++ b/docs/superpowers/specs/2026-05-14-redis-token-blacklist-design.md @@ -27,18 +27,14 @@ | Key 格式 | Value | TTL | |---------|-------|-----| -| `blacklist:token:{tokenHash}` | `{"user_id":123,"reason":"封禁原因"}` | 与 Token 剩余有效期一致 | - -**Key 结构说明:** -- `tokenHash`: 使用 SHA256 对 Token 进行哈希,避免使用长字符串作为 Key -- 这样做既保证了唯一性,又节省 Redis 内存 +| `blacklist:token:{tokenSignature}` | `{userID}:{banReason}` | 与 Token 过期时间一致 | **Value 结构说明:** -- JSON 格式存储,包含 `user_id` 和 `reason` 字段 -- 使用 JSON 格式避免 value 中包含特殊字符导致的解析问题 +- `userID`: 被封禁用户的 ID +- `banReason`: 封禁原因(可选,便于后续追踪) **TTL 说明:** -- TTL 与 Token 剩余有效期一致,Token 过期后自动清理黑名单记录 +- TTL 与 Token 过期时间一致,Token 过期后自动清理黑名单记录 ### 2.3 核心接口 @@ -113,9 +109,8 @@ package database import ( "context" - "crypto/sha256" - "encoding/json" "fmt" + "strconv" "time" "github.com/redis/go-redis/v9" @@ -127,35 +122,22 @@ const ( // BlacklistEntry 黑名单条目 type BlacklistEntry struct { - UserID int64 `json:"user_id"` - Reason string `json:"reason"` -} - -// tokenToHash 将 Token 转换为哈希作为 Key -func tokenToHash(token string) string { - hash := sha256.Sum256([]byte(token)) - return fmt.Sprintf("%x", hash) + UserID int64 + BanReason string } // AddToBlacklist 添加 Token 到黑名单 // token: JWT Token 字符串 // userID: 用户 ID // banReason: 封禁原因 -// ttl: Token 剩余有效期 +// ttl: Token 剩余有效期(秒) func AddToBlacklist(ctx context.Context, token string, userID int64, banReason string, ttl time.Duration) error { - if token == "" { - return fmt.Errorf("token is empty") - } if RedisClient == nil { return fmt.Errorf("redis client is not initialized") } - key := BlacklistKeyPrefix + tokenToHash(token) - entry := BlacklistEntry{UserID: userID, Reason: banReason} - value, err := json.Marshal(entry) - if err != nil { - return fmt.Errorf("failed to marshal blacklist entry: %w", err) - } + key := BlacklistKeyPrefix + token + value := fmt.Sprintf("%d:%s", userID, banReason) return RedisClient.Set(ctx, key, value, ttl).Err() } @@ -163,14 +145,11 @@ func AddToBlacklist(ctx context.Context, token string, userID int64, banReason s // IsBlacklisted 检查 Token 是否在黑名单 // 返回: (是否在黑名单, 用户ID, 封禁原因, error) func IsBlacklisted(ctx context.Context, token string) (bool, int64, string, error) { - if token == "" { - return false, 0, "", nil - } if RedisClient == nil { return false, 0, "", fmt.Errorf("redis client is not initialized") } - key := BlacklistKeyPrefix + tokenToHash(token) + key := BlacklistKeyPrefix + token value, err := RedisClient.Get(ctx, key).Result() if err == redis.Nil { return false, 0, "", nil @@ -179,24 +158,26 @@ func IsBlacklisted(ctx context.Context, token string) (bool, int64, string, erro return false, 0, "", err } - var entry BlacklistEntry - if err := json.Unmarshal([]byte(value), &entry); err != nil { - return false, 0, "", fmt.Errorf("failed to unmarshal blacklist entry: %w", err) + // 解析 value: "userID:banReason" + var userID int64 + var banReason string + _, parseErr := fmt.Sscanf(value, "%d:%s", &userID, &banReason) + if parseErr != nil { + // 兼容旧格式或只存储 userID 的情况 + userID, _ = strconv.ParseInt(value, 10, 64) + banReason = "" } - return true, entry.UserID, entry.Reason, nil + return true, userID, banReason, nil } // RemoveFromBlacklist 从黑名单移除 Token(用于解封) func RemoveFromBlacklist(ctx context.Context, token string) error { - if token == "" { - return nil - } if RedisClient == nil { return fmt.Errorf("redis client is not initialized") } - key := BlacklistKeyPrefix + tokenToHash(token) + key := BlacklistKeyPrefix + token return RedisClient.Del(ctx, key).Err() } ```