Express与Nginx已配置CORS 301重定向仍被跨域拦截如何解决
问题根因
- 跨域
fetch默认会自动跟随服务端返回的3xx重定向,整个跳转过程由浏览器自动执行,不会触发JS层的跳转逻辑;且浏览器会对重定向后的目标地址独立做CORS校验。从报错信息看,当前访问的页面源是http://www.example.com,但重定向目标是http://example.com,二者属于不同源,只要重定向后的响应没带对应源的CORS头,就会被拦截。 - Nginx配置存在逻辑漏洞:Nginx的
add_header指令遵循块继承规则——如果当前层级写了add_header,就不会继承外层的同名配置。单独在OPTIONS请求的if块里写了一套CORS头,和外层规则不统一,且外层的Access-Control-Allow-Origin直接取$http_origin没有做白名单校验,遇到重定向后请求头丢失、origin为空的场景,就不会返回正确的CORS头。另外301是永久重定向,浏览器会本地缓存重定向结果,甚至直接把POST请求转为GET请求,进一步加剧头丢失的问题。 - 前端代码存在低级错误:
fetch返回的Response实例是前端响应对象,根本没有redirect()方法,写的response.redirect(response.url)执行时会直接抛JS异常,完全达不到手动跳转的效果。 - 额外严重安全隐患:登录接口的SQL语句直接拼接用户传入的参数,存在SQL注入风险,攻击者可以构造恶意参数拖走整个用户库。
可落地方案
跨域场景下不要靠接口返回3xx重定向做页面跳转,改成接口返回JSON状态、前端收到成功标识后手动跳页,直接绕开重定向链路的CORS校验问题,这是前后端分离架构的标准实践:
- 改造后端Express登录接口,移除301重定向逻辑,统一返回JSON结果,同时修复SQL注入问题:
// CORS配置明确指定允许的源,不要直接用默认cors() app.use(cors({ origin: ['http://example.com', 'http://www.example.com'], credentials: true // 如果需要传Cookie、Session就开这个,同时不能把origin设为* })); app.post('/login', function(req, res) { // 用参数化查询,禁止直接拼接用户输入到SQL里 const query = 'select id, password from user where id = ?'; db.query(query, [req.body.id], (err, result) => { if (err) { console.log(err); return res.status(500).json({code: 500, msg: '服务端异常'}); } if (result.length === 0) { return res.json({code: 400, msg: '账号不存在'}); } if (req.body.pw === result[0].password) { // 登录成功直接返回成功标识,不要做重定向 // 要写Session、Set-Cookie直接在这里写就行 return res.json({code: 200, msg: '登录成功'}); } else { console.log("密码错误"); return res.json({code: 400, msg: '密码错误'}); } }); });
- 改造前端请求逻辑,删除无效的
response.redirect调用,收到登录成功的状态码后用window.location手动跳页:
const url = 'http://api.example.com/login'; fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', // 跨域传Cookie必须加这个配置 body: JSON.stringify(data), }) .then(response => response.json()) .then(res => { if (res.code === 200) { // 登录成功,手动跳转到主页 window.location.replace('http://example.com'); } else { // 这里处理账号不存在、密码错误的提示逻辑 console.log(res.msg); } }) .catch(err => { console.log('请求异常', err); });
- 修正Nginx的CORS配置,删除OPTIONS块里重复的CORS头配置,加origin白名单校验,避免规则覆盖:
location / { # 原有alias等配置保留 # 先做origin白名单校验,只允许信任的源跨域 set $cors_origin ""; if ($http_origin ~* "^http://(www\.)?example.com$") { set $cors_origin $http_origin; } # 所有响应统一加CORS头,加always参数保证错误响应也带头 add_header 'Access-Control-Allow-Origin' "$cors_origin" always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always; # OPTIONS预检请求单独处理 if ($request_method = 'OPTIONS') { add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } try_files $uri $uri/ $uri/index.html $uri.html =404; }
- 域名统一配置:把
example.com和www.example.com做301归一,比如所有www的请求都跳转到不带www的主域名,避免两个域名混用触发不必要的跨域问题。
内容的提问来源于stack exchange,提问作者Neang_Chi
相关产品推荐
相关产品推荐

