如何实现仅允许自身网站代码向Node.js/Express.js后端发起POST API请求
嘿,这个问题确实是前后端通信里的经典痛点——毕竟前端代码全在客户端,想藏密钥根本是白费功夫。结合你用Express的场景,我给你几个经过实战验证的可行方案,从基础防御到敏感操作加固都有:
这是应对这类需求的标准方案,核心思路是验证请求确实来自你的网站页面,而非第三方脚本或工具。
实现步骤:
- 后端生成随机CSRF令牌,存入
HttpOnly+SameSite类型的Cookie(HttpOnly防止前端JS读取,SameSite限制跨站请求携带Cookie) - 页面加载时,后端把令牌嵌入到页面DOM中(比如meta标签),让前端能获取到
- 前端发POST请求时,将令牌放到自定义请求头里(比如
X-CSRF-Token) - 后端中间件验证请求头的令牌与Cookie中的令牌是否一致
Express代码示例:
const express = require('express'); const cookieParser = require('cookie-parser'); const crypto = require('crypto'); const app = express(); app.use(cookieParser()); app.use(express.json()); // 页面路由:生成CSRF令牌并传给前端 app.get('/', (req, res) => { const csrfToken = crypto.randomBytes(32).toString('hex'); // 设置Cookie:生产环境记得开启secure(HTTPS下生效) res.cookie('csrfToken', csrfToken, { httpOnly: true, sameSite: 'strict', secure: process.env.NODE_ENV === 'production', maxAge: 3600000 // 1小时有效期 }); // 把令牌嵌入页面meta标签 res.send(` <html> <body> <meta name="csrf-token" content="${csrfToken}"> <button onclick="submitData()">提交数据</button> <script> function submitData() { const csrfToken = document.querySelector('meta[name="csrf-token"]').content; fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, body: JSON.stringify({ username: 'test' }) }); } </script> </body> </html> `); }); // CSRF验证中间件 const validateCsrf = (req, res, next) => { const cookieToken = req.cookies.csrfToken; const headerToken = req.headers['x-csrf-token']; if (!cookieToken || !headerToken || cookieToken !== headerToken) { return res.status(403).json({ error: 'Invalid CSRF token' }); } next(); }; // 受保护的POST接口 app.post('/api/submit', validateCsrf, (req, res) => { res.json({ success: true, receivedData: req.body }); }); app.listen(3000);
优势:
第三方无法跨域读取你的页面内容(同源策略限制),也就拿不到令牌;同时SameSite Cookie会阻止跨站请求携带Cookie,双重保障确保只有你的网站能合法提交请求。
方案2:验证请求来源(辅助防御)
通过检查请求的Origin或Referer头,判断请求是否来自你的域名。虽然这两个头可能被伪造,但浏览器在正常跨站请求时会限制Origin的传递,所以可以作为第一层防御。
Express代码示例:
const validateOrigin = (req, res, next) => { // 配置你的合法域名列表 const allowedOrigins = ['https://your-production-domain.com', 'http://localhost:3000']; const origin = req.headers.origin; const referer = req.headers.referer; // 优先验证Origin,再验证Referer if (origin && allowedOrigins.includes(origin)) { return next(); } if (referer && allowedOrigins.some(allowed => referer.startsWith(allowed))) { return next(); } return res.status(403).json({ error: 'Invalid request origin' }); }; // 给POST接口加上来源验证 app.post('/api/submit', validateOrigin, (req, res) => { res.json({ success: true }); });
注意:
生产环境记得移除开发环境的localhost域名,且不要单独依赖这个方案,要和CSRF令牌配合使用。
方案3:短时效一次性令牌(敏感操作专用)
如果是支付、修改密码这类高敏感操作,可以用一次性、短时效的令牌:用户触发操作前先请求后端获取临时令牌,提交时携带该令牌,后端验证后立即失效。
Express代码示例:
const express = require('express'); const crypto = require('crypto'); const app = express(); app.use(express.json()); // 用Set存储临时令牌(生产环境建议用Redis) const tempTokens = new Set(); // 获取临时令牌接口 app.get('/api/get-temp-token', (req, res) => { const tempToken = crypto.randomBytes(16).toString('hex'); tempTokens.add(tempToken); // 5分钟后自动过期 setTimeout(() => tempTokens.delete(tempToken), 300000); res.json({ tempToken }); }); // 敏感操作接口 app.post('/api/submit-sensitive', (req, res) => { const tempToken = req.headers['x-temp-token']; if (!tempToken || !tempTokens.has(tempToken)) { return res.status(403).json({ error: 'Invalid temporary token' }); } tempTokens.delete(tempToken); // 令牌只能用一次 res.json({ success: true, message: '敏感操作已执行' }); });
前端代码示例:
async function submitSensitiveData() { // 先获取临时令牌 const tokenRes = await fetch('/api/get-temp-token'); const { tempToken } = await tokenRes.json(); // 携带令牌提交请求 await fetch('/api/submit-sensitive', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Temp-Token': tempToken }, body: JSON.stringify({ password: 'new-password' }) }); }
优势:
即使令牌被窃取,也会很快过期且无法重复使用,极大降低风险。
内容的提问来源于stack exchange,提问作者SirGolem
相关产品推荐
相关产品推荐

