一、会话控制概念
- 目的:在无状态的 HTTP 请求间识别/鉴权用户身份并维持登录状态。
- 核心问题:谁保存“用户状态”?(服务器 / 客户端 / 第三方认证服务器),以及如何安全地在多请求间传递该凭证(Cookie / Authorization header)。
二、传统方案:Cookie + 服务端 Session
1) 流程
- 用户登录,后端验证凭证(用户名/密码)。
- 后端在服务器端创建 session(比如
sessionId对应的对象存在 Redis / DB / 内存),并返回Set-Cookie: sessionId=xxx; HttpOnly; Secure; SameSite=Lax; Path=/响应头部信息。 - 浏览器自动带上 Cookie(同源或按 CORS 配置带上
credentials: 'include'),后端根据sessionId在 session store 查到用户信息并授权。
2) 示例(Express + express-session + Redis)
后端:
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
app.use(session({store: new RedisStore({ client: redisClient }),secret: process.env.SESSION_SECRET,name: 'sid',resave: false,saveUninitialized: false,cookie: {httpOnly: true,secure: true, // 生产必须 httpssameSite: 'lax', // 或 'strict' / 'none'(配合跨域)maxAge: 24 * 3600 * 1000}
}));
前端(fetch):
fetch('/api/profile', { credentials: 'include' })
3) 优点
- 简单明确:后端完全控制会话(可以随时废弃/修改)。
- 支持服务端会话失效(logout 立刻生效)。
- 易于集成细粒度权限、会话统计与审计(存 Redis 的 session 可包含登录 IP、设备信息等)。
4) 缺点
- 水平扩展需共享 session store(Redis)。
- Session 存储和查询带来额外延迟与成本。
- 跨域场景复杂:需要正确配置
Access-Control-Allow-Credentials、Set-Cookie的SameSite=None; Secure等,避免安全问题。
三、JWT(JSON Web Token)方案概述
1) JWT 的基本思想(无状态 token)
- 后端发放一个签名的 token(通常是 Access Token),包含用户 id、到期时间等。
- 前端携带 token(通常放在 Authorization header 或者放入 HttpOnly cookie)向后端请求;后端不查 session store,而是直接验证 token 的签名与有效期,若通过则鉴权成功。
JWT 典型格式:header.payload.signature(Base64 编码)
const jwt = require('jsonwebtoken');
const accessToken = jwt.sign({ uid: xxx }, process.env.JWT_SECRET, { expiresIn: '15m' });
const payload = jwt.verify(accessToken, process.env.JWT_SECRET);
2) 常见拓展
- 短期 Access Token(如 5–15 分钟):放在内存或短寿命存储(不放 localStorage 更安全),用于 API 调用。
- 长期 Refresh Token(如 14 天或更长):安全保存(HttpOnly, Secure cookie),用于换取新的 Access Token。每次刷新都会发送新的 refresh token。服务端会保存 refresh token 的元数据(redis)。
- Access Token 可放在 Authorization:
Bearer <token>,或也可放在 HttpOnly cookie(同域 + credentials)推荐将 Refresh Token 放 HttpOnly cookie,Access Token 放内存并通过 Authorization header 发送(减少 CSRF 风险)。
3) 优点
- 无状态,后端不必保存 session(容易横向扩展,适合微服务 & 无状态后端),更适合移动端 SPA 等分布式鉴权。
- Token 自包含(本身携带用户身份信息,后端只需验证 token 签名,就直接知道 token 是否有效以及用户信息,无需再查询数据库或者 session 存储),减少每次请求的 DB 查询。
- 与 OAuth2 兼容,便于做 SSO 与第三方登录。
4) 缺点
- 无法轻易撤销(access token 在有效期内一直可用,除非引入黑名单/撤销机制)。
- 如果把 JWT 放在 localStorage(可被 JS 读取)会增加 XSS 风险;如果放 Cookie,则面临 CSRF 风险。
四、Cookie Session 与 JWT 的主要区别
表格 还在加载中,请等待加载完成后再尝试复制
五、安全策略
CSRF 防护(Cookie 自动带 credential 的情况下)
- 优先:使用 SameSite=Lax 或 SameSite=Strict 。
- 对于必须
SameSite=None的跨站场景,配合 CSRF Token(双 submit cookie):- 后端在登录时设
Set-Cookie: CSRF-Token=xxx; HttpOnly=false; Secure(可被 JS 读取) - 前端每次请求在 header 中带
X-CSRF-Token: <value>,后端验证 header 与 cookie 值一致。
- 后端在登录时设
- 或使用
Origin/Referer检查(只允许来自可信域的请求)。
核心:CSRF 攻击者 可以发请求,但 没法读 Cookie,无法把 token 放到 header 中,所以校验(
(req.cookies["CSRF-Token"] !== req.headers["X-CSRF-Token"]))。CSRF 攻击能发请求,但拿不到 cookie 内容 。
XSS 防护(避免 token 被窃取)
- 对可执行 token 的存储避免使用
localStorage(HttpOnly cookie 防止 JS 读取)。
JWT 可撤销性策略
- 短寿命 access token + refresh token(refresh token 存 HttpOnly cookie):
- access token 过期快,可减少滥用窗口。
- refresh token 在服务端有黑名单/DB 记录,必要时撤销(例如用户登出、密码变更)。
- token id(jti) + 存在服务端黑名单(Redis):在 logout / 强制下线时把 jti 写入黑名单并在验证时检查。黑名单可设置过期与分布式缓存。