fix: 会话时效30天 + 多设备同时登录

- access token 从 30 分钟延长到 30 天
- refresh token 从 7 天延长到 30 天
- refresh token 从 MySQL 单字段改为 Redis Set 存储,支持多设备同时登录
- 单设备退出不再踢掉其他设备(只删自己的 Redis hash)
- 改密码/禁用 TOTP 仍会让所有设备掉线(token_version 递增)
- 移除 reuse 误杀逻辑:版本不匹配只拒绝当前 token,不惩罚其他设备

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-05-29 18:52:14 +08:00
parent 3c7036babf
commit 152852e2c3
2 changed files with 113 additions and 63 deletions
+9 -3
View File
@@ -289,12 +289,18 @@ async def change_password(
new_hash = bcrypt.hashpw(payload.new_password.encode(), bcrypt.gensalt()).decode()
current_admin.password_hash = new_hash
# Security: invalidate all existing sessions (refresh tokens + JWT updated_at check)
# Security: invalidate all existing sessions (refresh tokens + JWT token_version)
current_admin.token_version += 1
current_admin.jwt_refresh_token = None
current_admin.jwt_token_expires = None
await admin_repo.update(current_admin)
# Clear all refresh tokens from Redis (all devices logged out)
try:
from server.infrastructure.redis.client import get_redis
redis = get_redis()
await redis.delete(f"refresh_tokens:{admin.id}")
except Exception:
logger.warning(f"Failed to clear refresh tokens from Redis for admin {admin.id}", exc_info=True)
# Audit log with client IP
ip_address = request.client.host if request.client else ""
audit_repo = AuditLogRepositoryImpl(db)