真正决定系统安全性的,是 **登录后的“确权”能力**。
> **缺少一个统一、可控、具备时效性的登录确权中间件**
一、什么是登录确权中间件?
登录 vs 确权
登录确权中间件的职责
二、为什么用 Redis 控制时效性?
仅用 JWT 的问题
登录成功 → 返回 JWT → 客户端每次请求携带
Redis 的优势
JWT 负责“身份声明”,Redis 负责“状态控制”
三、整体设计思路
登录流程
用户登录↓服务端校验账号密码↓生成 token↓token 存入 Redis(带 TTL)↓返回 token 给客户端
请求确权流程
请求进入↓登录确权中间件↓解析 token↓从 Redis 校验 token 是否存在 & 是否过期↓通过 → 放行失败 → 拦截
四、Redis 数据结构设计
方案一:token 作为 key(推荐)
login:token:{token} => userInfoTTL: 2h
login:token:abc123 => { userId: 1001, role: "admin" }
方案二:userId 作为 key(多端控制)
login:user:{userId}:{device} => token
五、登录接口实现(示例)
const token = uuid();await redis.set( login:token:</span><span class="code-snippet__string"><span class="code-snippet__subst">${token}</span></span><span class="code-snippet__string">, JSON.stringify({ userId, role }), 'EX', 7200 );ctx.body = { token };
六、登录确权中间件核心实现(Koa)
中间件职责拆解
登录确权中间件代码示例
async function authMiddleware(ctx, next) { const token = ctx.headers.authorization; if (!token) { ctx.status = 401; ctx.body = { message: '未登录' }; return; } const key = login:token:</span><span class="code-snippet__string"><span class="code-snippet__subst">${token}</span></span><span class="code-snippet__string">; const userStr = await redis.get(key); if (!userStr) { ctx.status = 401; ctx.body = { message: '登录已过期' }; return; } const user = JSON.parse(userStr); ctx.state.user = user; await redis.expire(key, 7200); await next();}
七、滑动过期(非常重要)
什么是滑动过期?
八、主动失效设计(退出登录)
退出登录
await redis.del(login:token:</span><span class="code-snippet__string"><span class="code-snippet__subst">${token}</span></span><span class="code-snippet__string">);
修改密码 / 风控封禁
九、多端登录控制(进阶)
单点登录设计
login:user:{userId} => token
示例逻辑
const oldToken = await redis.get(login:user:</span><span class="code-snippet__string"><span class="code-snippet__subst">${userId}</span></span><span class="code-snippet__string">);if (oldToken) { await redis.del(login:token:</span><span class="code-snippet__string"><span class="code-snippet__subst">${oldToken}</span></span><span class="code-snippet__string">);}await redis.set(login:user:</span><span class="code-snippet__string"><span class="code-snippet__subst">${userId}</span></span><span class="code-snippet__string">, token);await redis.set(login:token:</span><span class="code-snippet__string"><span class="code-snippet__subst">${token}</span></span><span class="code-snippet__string">, userInfo, 'EX', 7200);
十、白名单 & 路由跳过
不需要确权的接口
中间件中处理
const whiteList = ['/login', '/register']; if (whiteList.includes(ctx.path)) { return next();}
十一、安全与工程实践
1️⃣ token 设计
2️⃣ token 存放位置
Authorization: Bearer xxx
3️⃣ Redis Key 规范化
4️⃣ 不在 Redis 中存敏感信息
十二、中间件加载顺序
app.use(errorHandler);app.use(authMiddleware);app.use(router.routes());
十三、方案总结
文章评论