fix: 前后台 Token Redis 存储隔离,修复同账号互相覆盖问题

核心变更:
- Redis key 加入 clientType 前缀:echo:auth:token:{frontend|admin}:{user_id}
- JWT Claims 新增 client_type 字段区分前台/管理端
- 新增 constants/client_type.go 定义 ClientTypeFrontend / ClientTypeAdmin
- 全链路传递 clientType:Controller → Service → TokenStore → Redis
- 中间件从 JWT Claims 提取 clientType 做 Redis 校验
- 登出只删除当前端 Token,不影响另一端

前端变更:
- 管理端 login API 改为调用 POST /api/v1/admin/auth/login(之前错误调用了前台接口)

文档更新:
- 系统设计文档、实施计划、API文档、开发规范、项目进度全部同步

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-03-02 14:28:45 +08:00
parent cfb1651480
commit c3e8600b24
13 changed files with 206 additions and 76 deletions

View File

@@ -139,7 +139,7 @@
**权限:** 需认证
**说明:** 采用有状态 JWT 方案Token 存储在 Redis 中(`echo:auth:token:{user_id}``echo:auth:refresh:{user_id}`)。登出时服务端会从 Redis 中删除该用户的 Access Token 和 Refresh Token使其立即失效。客户端也应同步清除本地存储的 Token。
**说明:** 采用有状态 JWT 方案Token 按 clientType 隔离存储在 Redis 中(`echo:auth:token:frontend:{user_id}``echo:auth:refresh:frontend:{user_id}`)。登出时服务端只删除前台端的 Token不影响管理端。客户端也应同步清除本地存储的 Token。
**成功响应:**
```json

View File

@@ -2,7 +2,7 @@
> **适用范围**EchoChat 项目全端Go 后端 + admin 管理端 + frontend 用户端)
> **创建日期**2026-03-02
> **最后更新**2026-03-02
> **最后更新**2026-03-02(新增前后台 Token 隔离规范)
---
@@ -202,3 +202,66 @@ if err != nil {
| 用户不存在 | 404 | 用户不存在 | 显示 message |
| 服务器内部错误 | 500 | 获取用户列表失败 | 显示 message |
| 网络异常 | - | (无响应体) | 显示前端 fallback 文案 |
---
## 六、前后台 Token 隔离规范
### 6.1 核心原则
前台用户端frontend和后台管理端admin的 Token **必须在 Redis 中完全隔离**,同一用户可以同时在两端保持登录状态,互不影响。
### 6.2 Redis Key 格式
```
echo:auth:token:{client_type}:{user_id} → Access Token
echo:auth:refresh:{client_type}:{user_id} → Refresh Token
```
| client_type | 说明 | 示例 Key |
|------------|------|----------|
| `frontend` | 前台用户端 | `echo:auth:token:frontend:1` |
| `admin` | 后台管理端 | `echo:auth:token:admin:1` |
### 6.3 JWT Claims 中的 client_type
JWT Token 的 Claims 中包含 `client_type` 字段,用于:
- 中间件校验时定位正确的 Redis key
- 登出时只删除当前端的 Token
- Refresh Token 刷新时保持 client_type 不变
```json
{
"user_id": 1,
"username": "admin_test",
"roles": ["user", "admin"],
"client_type": "admin",
"sub": "access",
"exp": 1709400000,
"iss": "echochat"
}
```
### 6.4 API 路由区分
| 端 | 登录 API | 说明 |
|----|---------|------|
| 前台 | `POST /api/v1/auth/login` | clientType=frontend |
| 管理端 | `POST /api/v1/admin/auth/login` | clientType=admin额外检查管理员角色 |
### 6.5 登出行为
- 前台登出:只删除 `echo:auth:token:frontend:{user_id}`,不影响管理端
- 管理端登出:只删除 `echo:auth:token:admin:{user_id}`,不影响前台
- 管理端禁用用户:应删除该用户两端的所有 Token由管理模块负责
### 6.6 检查清单
新增认证相关功能时,必须确认:
- [ ] Login 方法传递了正确的 clientType
- [ ] 前端调用了正确的登录 API 端点
- [ ] Redis key 包含 clientType 前缀
- [ ] JWT Claims 中包含 client_type 字段
- [ ] 登出时只删除对应 clientType 的 Token
- [ ] Token 刷新时保持原 clientType 不变

View File

@@ -573,9 +573,10 @@ COMMENT ON COLUMN admin_operation_logs.created_at IS '操作时间';
### 4.2 Redis 数据结构
```
# 用户认证
echo:auth:token:{user_id} → JWT Token (STRING, TTL 7天)
echo:auth:refresh:{user_id} → Refresh Token (STRING, TTL 30天)
# 用户认证(按 client_type 隔离前台和管理端)
echo:auth:token:{client_type}:{user_id} → JWT Access Token (STRING, TTL 由配置决定)
echo:auth:refresh:{client_type}:{user_id} → Refresh Token (STRING, TTL 由配置决定)
# client_type: frontend前台用户端/ admin后台管理端
# 用户在线状态
echo:user:online → 在线用户集合 (SET)

View File

@@ -507,11 +507,11 @@ type UserInfo struct {
**Step 4: 创建 Token Redis 存储管理**
创建 `app/auth/service/token_store.go`
- 遵循设计方案中的 Redis Key 规范`echo:auth:token:{user_id}` / `echo:auth:refresh:{user_id}`
- `SaveTokens(ctx, userID, accessToken, refreshToken)` — 存入 Redis覆盖旧 Token实现单设备登录
- `ValidateAccessToken(ctx, userID, token)` — 校验 Access Token 与 Redis 一致
- `ValidateRefreshToken(ctx, userID, token)` — 校验 Refresh Token 与 Redis 一致
- `RemoveTokens(ctx, userID)` — 登出时删除 Redis 中的 Token
- Redis Key 按 clientType 隔离`echo:auth:token:{client_type}:{user_id}` / `echo:auth:refresh:{client_type}:{user_id}`
- `SaveTokens(ctx, userID, clientType, accessToken, refreshToken)` — 存入 Redis按 clientType 隔离
- `ValidateAccessToken(ctx, userID, clientType, token)` — 校验 Access Token 与 Redis 一致
- `ValidateRefreshToken(ctx, userID, clientType, token)` — 校验 Refresh Token 与 Redis 一致
- `RemoveTokens(ctx, userID, clientType)` — 登出时删除指定 clientType 的 Token
**Step 5: 创建认证服务**

View File

@@ -29,7 +29,7 @@
### 后端Go
1. **框架组合**Gin + GORM + Wire + Zap + Viper
2. **JWT 策略**:有状态 JWTToken 存储在 Redis`echo:auth:token:{user_id}`
2. **JWT 策略**:有状态 JWTToken 按 clientType 隔离存储在 Redis`echo:auth:token:{frontend|admin}:{user_id}`
3. **密码加密**bcrypt
4. **数据库时间精度**`TIMESTAMP(0)` 精确到秒
5. **API 响应格式**:统一 `{ "code": 0, "message": "success", "data": ... }`