REST API 中的 CSRF 防护与传统 Web 应用有所不同,因为 REST API 通常使用 JSON 格式进行数据交换,并且可能被各种客户端(Web、移动应用、第三方服务)调用。
REST API 中 CSRF 的特殊性
1. 客户端多样性
- Web 应用:浏览器环境,自动发送 Cookie
- 移动应用:原生环境,需要手动管理认证
- 第三方服务:API 调用,可能使用不同的认证方式
2. 请求格式
- 传统 Web:表单提交,Content-Type: application/x-www-form-urlencoded
- REST API:JSON 数据,Content-Type: application/json
3. 认证方式
- Cookie 认证:容易受到 CSRF 攻击
- Token 认证:JWT、OAuth 等,相对安全
- 混合认证:Cookie + Token
REST API CSRF 防护策略
1. 使用 Token 认证(推荐)
javascript// JWT Token 认证 function authenticateJWT(req, res, next) { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { return res.status(401).send('No token provided'); } try { const decoded = jwt.verify(token, JWT_SECRET); req.user = decoded; next(); } catch (error) { return res.status(401).send('Invalid token'); } } // 保护路由 app.post('/api/transfer', authenticateJWT, (req, res) => { // 处理转账请求 });
优势:
- Token 存储在客户端(localStorage 或内存)
- 不会自动发送,天然防护 CSRF
- 适合移动应用和第三方集成
2. 自定义请求头验证
javascript// 生成 CSRF Token function generateCSRFToken() { return crypto.randomBytes(32).toString('hex'); } // 设置 Token app.get('/api/csrf-token', (req, res) => { const token = generateCSRFToken(); req.session.csrfToken = token; res.json({ csrfToken: token }); }); // 验证自定义头 function validateCSRFHeader(req, res, next) { const token = req.headers['x-csrf-token']; if (!token || token !== req.session.csrfToken) { return res.status(403).send('Invalid CSRF token'); } next(); } // 保护路由 app.post('/api/transfer', validateCSRFHeader, (req, res) => { // 处理请求 });
前端实现:
javascript// 获取 Token async function getCSRFToken() { const response = await fetch('/api/csrf-token'); const data = await response.json(); return data.csrfToken; } // 发送请求 async function makeRequest() { const token = await getCSRFToken(); await fetch('/api/transfer', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': token }, body: JSON.stringify({ to: 'user123', amount: 100 }) }); }
3. SameSite Cookie 配置
javascriptapp.use(session({ secret: 'secret', cookie: { httpOnly: true, secure: true, sameSite: 'strict' // 或 'lax' } }));
4. Origin 头验证
javascriptfunction validateOrigin(req, res, next) { const origin = req.headers.origin; const allowedOrigins = ['https://example.com', 'https://app.example.com']; if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') { return next(); } if (!origin || !allowedOrigins.includes(origin)) { return res.status(403).send('Invalid origin'); } next(); }
不同场景的防护方案
场景 1:纯 Web 应用(浏览器)
javascript// 使用 Cookie + CSRF Token app.use(session({ secret: 'secret', cookie: { httpOnly: true, secure: true, sameSite: 'lax' } })); app.use(csrf({ cookie: true })); app.post('/api/transfer', (req, res) => { // req.csrfToken() 可用于前端 });
场景 2:移动应用 + Web 应用
javascript// 混合认证策略 function authenticate(req, res, next) { // 优先使用 JWT Token const authHeader = req.headers.authorization; if (authHeader) { return authenticateJWT(req, res, next); } // 回退到 Cookie 认证 if (req.session.userId) { req.user = { id: req.session.userId }; return next(); } return res.status(401).send('Authentication required'); } // CSRF 防护仅对 Cookie 认证生效 function csrfProtection(req, res, next) { if (req.headers.authorization) { // JWT 认证,跳过 CSRF 验证 return next(); } // Cookie 认证,需要 CSRF 验证 if (req.body._csrf !== req.session.csrfToken) { return res.status(403).send('Invalid CSRF token'); } next(); }
场景 3:第三方 API 集成
javascript// API Key 认证 function authenticateAPIKey(req, res, next) { const apiKey = req.headers['x-api-key']; if (!apiKey) { return res.status(401).send('API key required'); } // 验证 API Key const user = await validateAPIKey(apiKey); if (!user) { return res.status(401).send('Invalid API key'); } req.user = user; next(); } // API Key 认证不需要 CSRF 防护 app.post('/api/transfer', authenticateAPIKey, (req, res) => { // 处理请求 });
CORS 配置与 CSRF
javascriptconst corsOptions = { origin: function (origin, callback) { const allowedOrigins = ['https://example.com', 'https://app.example.com']; // 允许没有 origin 的请求(如移动应用) if (!origin) return callback(null, true); if (allowedOrigins.indexOf(origin) !== -1) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, credentials: true, // 允许发送 Cookie methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token'] }; app.use(cors(corsOptions));
最佳实践总结
- 优先使用 Token 认证:JWT、OAuth 等天然防护 CSRF
- Cookie 认证必须防护:SameSite + CSRF Token
- 自定义请求头:比表单字段更安全
- Origin 头验证:补充防护措施
- CORS 正确配置:限制允许的来源
- 分层防护:多种措施组合使用
REST API 的 CSRF 防护需要根据具体的使用场景和客户端类型来选择合适的策略,没有通用的解决方案。