Node.js/Express:安全校验请求是否来自同域的方法
验证Express接口的同域请求
嘿,这个需求我之前在项目里也碰到过,用req.headers.referer来校验同域请求的思路是可行的,但得先聊聊它的利弊,再给你具体的实现方案,还有更可靠的补充手段~
一、Referer头的优缺点
- 优势:实现起来特别简单,同域的XMLHttpRequest请求通常会自动带上正确的Referer头(只要用户浏览器没禁用这个字段)
- 劣势:
- 部分浏览器会出于隐私设置不发送Referer,或者对其进行修改
- 一些代理、CDN服务也可能剥离或篡改这个头
- 非浏览器发起的恶意请求可以轻松伪造Referer(虽然浏览器的同源策略会限制,但爬虫、Postman这类工具可不管这个)
所以这个方法可以作为第一层的快速校验,但绝对不能作为唯一的安全屏障,尤其是涉及敏感数据的接口,必须结合access token验证这类更可靠的手段。
二、具体实现代码
你可以写一个Express中间件来处理这个逻辑:检查请求是否来自允许的同域,是就直接放行;不是的话,就要求对方提供合法的access token。
const express = require('express'); const app = express(); // 定义你的允许的同域域名(比如你的前端页面部署的域名,本地开发和生产环境要分开) const ALLOWED_ORIGINS = ['http://localhost:3000', 'https://your-production-app.com']; // 同域校验+token验证中间件 const validateRequestOrigin = (req, res, next) => { const referer = req.headers.referer; // 判断是否为同域请求:检查Referer是否以允许的域名开头 const isSameDomain = referer ? ALLOWED_ORIGINS.some(origin => referer.startsWith(origin)) : false; // 同域请求直接放行 if (isSameDomain) { return next(); } // 非同源请求,必须验证access token const accessToken = req.headers['authorization']?.split(' ')[1]; // 假设用Bearer Token格式 if (!accessToken || !verifyAccessToken(accessToken)) { // 替换成你的实际token验证逻辑 return res.status(401).json({ error: 'Unauthorized: 请提供有效的访问令牌' }); } next(); }; // 模拟token验证函数,实际项目里可以用jsonwebtoken这类库实现 const verifyAccessToken = (token) => { // 这里写你的校验逻辑:验证签名、过期时间、用户权限等 return token === 'your-valid-production-token'; // 示例,别直接用这个! }; // 把中间件应用到所有REST接口路由上 app.use('/api', validateRequestOrigin); // 示例REST接口 app.get('/api/user/profile', (req, res) => { res.json({ username: 'test-user', email: 'test@example.com' }); }); // 服务HTML页面的路由 app.get('/', (req, res) => { res.sendFile(__dirname + '/public/index.html'); }); app.listen(3000, () => { console.log('服务启动在3000端口啦!'); });
三、更可靠的补充方案
1. 结合CORS配置
Express可以用cors包来精细化控制跨域规则,让浏览器自动拦截非法的跨域请求,再搭配我们的中间件做二次校验:
const cors = require('cors'); const corsOptions = { origin: function (origin, callback) { // 允许同域请求(origin为null是部分浏览器同域请求的特殊情况),或者你信任的外部域名 if (!origin || ALLOWED_ORIGINS.includes(origin)) { callback(null, true); } else { // 非允许的origin,浏览器会自动拦截,后续我们的中间件还会做token校验 callback(new Error('Not allowed by CORS')); } }, credentials: true // 允许携带cookie(如果你的同域请求需要的话) }; // 先应用CORS中间件,再应用我们的校验中间件 app.use('/api', cors(corsOptions), validateRequestOrigin);
2. 使用CSRF令牌(针对同域XHR请求)
如果你的同域请求涉及敏感操作(比如修改数据),用CSRF令牌比Referer头更可靠——因为令牌是服务端生成的,前端页面渲染时注入,XHR请求必须带上这个令牌才能通过校验。可以用csurf包实现:
const csurf = require('csurf'); const cookieParser = require('cookie-parser'); // 先解析cookie,因为csurf需要用cookie存储令牌 app.use(cookieParser()); // 初始化CSRF中间件 const csrfProtection = csurf({ cookie: true }); // 渲染HTML页面时,把CSRF令牌传给前端 app.get('/', csrfProtection, (req, res) => { res.send(` <html> <body> <script> // 发起XHR请求时,带上CSRF令牌 const xhr = new XMLHttpRequest(); xhr.open('POST', '/api/user/update'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('X-CSRF-Token', '${req.csrfToken()}'); xhr.send(JSON.stringify({ username: 'new-name' })); </script> </body> </html> `); }); // 修改我们的中间件,同域请求校验CSRF令牌,非同源请求校验token const validateRequestOrigin = (req, res, next) => { const referer = req.headers.referer; const isSameDomain = referer ? ALLOWED_ORIGINS.some(origin => referer.startsWith(origin)) : false; if (isSameDomain) { // 同域请求校验CSRF令牌 return csrfProtection(req, res, next); } // 非同源请求校验access token const accessToken = req.headers['authorization']?.split(' ')[1]; if (!accessToken || !verifyAccessToken(accessToken)) { return res.status(401).json({ error: 'Unauthorized: 无效或缺失访问令牌' }); } next(); }; app.use('/api', cors(corsOptions), validateRequestOrigin);
总结
Referer头可以作为快速的同域校验手段,但不能单独依赖- 结合CORS配置能在浏览器层面拦截大部分非法跨域请求
- 敏感接口一定要搭配access token验证,同域的敏感操作建议再加CSRF令牌提升安全性
内容的提问来源于stack exchange,提问作者lviggiani
相关产品推荐
相关产品推荐

