# Redis Token 黑名单实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 实现 JWT Token 黑名单功能,用于账号封禁和强制下线场景 **Architecture:** 在 gateway 层集成 Redis,JWT 中间件每次请求检查 Token 是否在黑名单。Redis Key 使用 SHA256 哈希存储,Value 使用 JSON 格式。 **Tech Stack:** Go, github.com/redis/go-redis/v9, gin middleware --- ## 文件结构 ``` backend/pkg/database/ ├── database.go # 现有 PostgreSQL(GORM) └── redis.go # 新建:Redis 客户端 + Token 黑名单 backend/gateway/config/config.go # 修改:添加 Redis 配置 backend/gateway/middleware/ # 修改:JWT 中间件检查黑名单 backend/gateway/main.go # 修改:初始化 Redis ``` --- ### Task 1: 创建 Redis 客户端和 Token 黑名单模块 **Files:** - Create: `backend/pkg/database/redis.go` - [ ] **Step 1: 创建 redis.go 文件** ```go package database import ( "context" "crypto/sha256" "encoding/json" "fmt" "time" "github.com/redis/go-redis/v9" ) const ( BlacklistKeyPrefix = "blacklist:token:" ) // RedisClient Redis 客户端单例 var RedisClient *redis.Client // Config Redis 配置 type RedisConfig struct { Host string Port int Password string DB int } // InitRedis 初始化 Redis 连接 func InitRedis(cfg RedisConfig) error { RedisClient = redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, DB: cfg.DB, }) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := RedisClient.Ping(ctx).Err(); err != nil { return fmt.Errorf("failed to connect to redis: %w", err) } return nil } // CloseRedis 关闭 Redis 连接 func CloseRedis() error { if RedisClient != nil { return RedisClient.Close() } return nil } // GetRedis 获取 Redis 客户端实例 func GetRedis() *redis.Client { return RedisClient } // RedisHealthCheck 健康检查 func RedisHealthCheck() error { if RedisClient == nil { return fmt.Errorf("redis client is not initialized") } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() return RedisClient.Ping(ctx).Err() } // BlacklistEntry 黑名单条目 type BlacklistEntry struct { UserID int64 `json:"user_id"` Reason string `json:"reason"` } // tokenToHash 将 Token 转换为 SHA256 哈希作为 Key func tokenToHash(token string) string { hash := sha256.Sum256([]byte(token)) return fmt.Sprintf("%x", hash) } // AddToBlacklist 添加 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) } return RedisClient.Set(ctx, key, value, ttl).Err() } // IsBlacklisted 检查 Token 是否在黑名单 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) value, err := RedisClient.Get(ctx, key).Result() if err == redis.Nil { return false, 0, "", nil } if err != nil { 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) } 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 + tokenToHash(token) return RedisClient.Del(ctx, key).Err() } ``` - [ ] **Step 2: 提交代码** ```bash git add backend/pkg/database/redis.go git commit -m "feat: 添加 Redis 客户端和 Token 黑名单模块 Co-Authored-By: Claude Opus 4.6 " ``` --- ### Task 2: 添加 Redis 配置到 config.go **Files:** - Modify: `backend/gateway/config/config.go` - [ ] **Step 1: 添加 RedisConfig 结构体到 Config 结构体** 在 `Config` 结构体中添加 `Redis` 字段: ```go type Config struct { Server ServerConfig Dubbo DubboConfig JWT JWTConfig OSS OSSConfig Redis RedisConfig // 新增 Root string } ``` - [ ] **Step 2: 添加 RedisConfig 结构体定义** 在 `ServerConfig` 之前添加: ```go // RedisConfig Redis 配置 type RedisConfig struct { Host string Port int Password string DB int } ``` - [ ] **Step 3: 在 Load() 函数中添加 Redis 配置加载** 在 return 语句的 OSS 配置后添加: ```go Redis: RedisConfig{ Host: getEnv("REDIS_HOST", "127.0.0.1"), Port: getEnvInt("REDIS_PORT", 6379), Password: getEnv("REDIS_PASSWORD", ""), DB: getEnvInt("REDIS_DB", 0), }, ``` - [ ] **Step 4: 提交代码** ```bash git add backend/gateway/config/config.go git commit -m "feat: 添加 Redis 配置项 Co-Authored-By: Claude Opus 4.6 " ``` --- ### Task 3: 在 main.go 中初始化 Redis **Files:** - Modify: `backend/gateway/main.go` - [ ] **Step 1: 添加 database 包导入** 在导入部分添加: ```go "github.com/topfans/backend/pkg/database" ``` - [ ] **Step 2: 在加载配置后初始化 Redis** 在 `cfg.Validate()` 之后,初始化 Dubbo clients 之前添加: ```go // 3.5 初始化 Redis logger.Logger.Info("Connecting to Redis...") if err := database.InitRedis(database.RedisConfig{ Host: cfg.Redis.Host, Port: cfg.Redis.Port, Password: cfg.Redis.Password, DB: cfg.Redis.DB, }); err != nil { logger.Logger.Fatal("Failed to connect to Redis", zap.Error(err)) } logger.Logger.Info("Redis connected successfully") defer database.CloseRedis() ``` - [ ] **Step 3: 提交代码** ```bash git add backend/gateway/main.go git commit -m "feat: 初始化 Redis 连接 Co-Authored-By: Claude Opus 4.6 " ``` --- ### Task 4: 在 JWT 中间件中添加黑名单检查 **Files:** - Modify: `backend/gateway/middleware/auth_middleware.go` - [ ] **Step 1: 添加 database 包导入** ```go "github.com/topfans/backend/pkg/database" ``` - [ ] **Step 2: 在 ParseToken 成功后、检查用户信息存入 gin.Context 之前添加黑名单检查** 在 `// 4. 将用户信息存入 gin.Context` 之前添加: ```go // 4. 检查 Token 是否在黑名单 isBlacklisted, bannedUserID, banReason, err := database.IsBlacklisted(c.Request.Context(), token) if err != nil { logger.Logger.Error("Failed to check blacklist", zap.String("path", c.Request.URL.Path), zap.Error(err), ) // Redis 错误时fail-open,允许请求继续(可根据安全策略调整) } if isBlacklisted { logger.Logger.Warn("Token is blacklisted", zap.Int64("banned_user_id", bannedUserID), zap.String("ban_reason", banReason), zap.String("path", c.Request.URL.Path), ) response.Unauthorized(c, "账号已被封禁") c.Abort() return } ``` - [ ] **Step 3: 提交代码** ```bash git add backend/gateway/middleware/auth_middleware.go git commit -m "feat: JWT 中间件添加 Token 黑名单检查 Co-Authored-By: Claude Opus 4.6 " ``` --- ### Task 5: 添加 Redis go.mod 依赖 **Files:** - Modify: `backend/gateway/go.mod` - [ ] **Step 1: 添加 redis 依赖** ```bash cd backend/gateway && go get github.com/redis/go-redis/v9@latest ``` - [ ] **Step 2: 提交代码** ```bash git add backend/gateway/go.mod backend/gateway/go.sum git commit -m "deps: 添加 redis go-redis/v9 依赖 Co-Authored-By: Claude Opus 4.6 " ``` --- ### Task 6: 验证构建 - [ ] **Step 1: 运行 go build 验证代码编译** ```bash cd backend/gateway && go build ./... ``` Expected: 编译成功,无错误 --- ## 变更记录 | 日期 | 变更内容 | |------|---------| | 2026-05-14 | 初始实现计划 |