docs: 优化 Redis Token 黑名单设计
- 使用 SHA256 哈希代替原始 token 作为 Key - 使用 JSON 格式存储 value,避免解析问题 - 添加输入验证 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
00a39e1819
commit
c38ad5a654
@ -27,14 +27,18 @@
|
||||
|
||||
| Key 格式 | Value | TTL |
|
||||
|---------|-------|-----|
|
||||
| `blacklist:token:{tokenSignature}` | `{userID}:{banReason}` | 与 Token 过期时间一致 |
|
||||
| `blacklist:token:{tokenHash}` | `{"user_id":123,"reason":"封禁原因"}` | 与 Token 剩余有效期一致 |
|
||||
|
||||
**Key 结构说明:**
|
||||
- `tokenHash`: 使用 SHA256 对 Token 进行哈希,避免使用长字符串作为 Key
|
||||
- 这样做既保证了唯一性,又节省 Redis 内存
|
||||
|
||||
**Value 结构说明:**
|
||||
- `userID`: 被封禁用户的 ID
|
||||
- `banReason`: 封禁原因(可选,便于后续追踪)
|
||||
- JSON 格式存储,包含 `user_id` 和 `reason` 字段
|
||||
- 使用 JSON 格式避免 value 中包含特殊字符导致的解析问题
|
||||
|
||||
**TTL 说明:**
|
||||
- TTL 与 Token 过期时间一致,Token 过期后自动清理黑名单记录
|
||||
- TTL 与 Token 剩余有效期一致,Token 过期后自动清理黑名单记录
|
||||
|
||||
### 2.3 核心接口
|
||||
|
||||
@ -109,8 +113,9 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
@ -122,22 +127,35 @@ const (
|
||||
|
||||
// BlacklistEntry 黑名单条目
|
||||
type BlacklistEntry struct {
|
||||
UserID int64
|
||||
BanReason string
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 + token
|
||||
value := fmt.Sprintf("%d:%s", userID, banReason)
|
||||
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)
|
||||
}
|
||||
|
||||
return RedisClient.Set(ctx, key, value, ttl).Err()
|
||||
}
|
||||
@ -145,11 +163,14 @@ 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 + token
|
||||
key := BlacklistKeyPrefix + tokenToHash(token)
|
||||
value, err := RedisClient.Get(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
return false, 0, "", nil
|
||||
@ -158,26 +179,24 @@ func IsBlacklisted(ctx context.Context, token string) (bool, int64, string, erro
|
||||
return false, 0, "", 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 = ""
|
||||
var entry BlacklistEntry
|
||||
if err := json.Unmarshal([]byte(value), &entry); err != nil {
|
||||
return false, 0, "", fmt.Errorf("failed to unmarshal blacklist entry: %w", err)
|
||||
}
|
||||
|
||||
return true, userID, banReason, nil
|
||||
return true, entry.UserID, entry.Reason, 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 + token
|
||||
key := BlacklistKeyPrefix + tokenToHash(token)
|
||||
return RedisClient.Del(ctx, key).Err()
|
||||
}
|
||||
```
|
||||
|
||||
Loading…
Reference in New Issue
Block a user