|
|
这几年折腾 xiuno 的人应该都踩过“跨域”这个坑:前后端分离、静态资源上 CDN、后台在子域名,结果一到登录、上传、AJAX 就各种 403、Cookie 丢失、预检失败。表面看是浏览器在“作妖”,本质是同源策略+服务端 CORS/会话配置没对齐。这里把我踩坑
清单理一理,给后来者留个参照。
第一,先分清“谁在跨谁”。很多人一上来就在 nginx 里全局加 Access-Control-Allow-Origin:*,结果登录还是不行。原因很简单:通配符不允许携带凭证,浏览器自动把 Cookie 丢了。xiuno 默认用基于 Cookie 的会话,涉及用户态的接口(login、post、upload)都需要 withCredentials。因此服务端必须按请求头里的 Origin 回显精确来源,同时加上 Access-Control-Allow-Credentials:true。我的做法是:只给前端域名白名单放行,其他一概 403,省心。
第二,OPTIONS 预检要“真支持”。很多人忽略了预检,导致浏览器在正式请求前就被拦了。nginx 层面要单独匹配 OPTIONS,直接 204 返回,带上 Allow-Methods、Allow-Headers(把常见的 X-Requested-With、Content-Type、Authorization 都列上),并且不要把预检转发到 PHP,减少压力。PHP 入口也最好兜底处理一下,避免某些路径绕过。
第三,Cookie 的 domain/path/secure 三件套。xiuno 的 setcookie 如果用默认,可能只在当前子域有效。跨子域访问时需要把 domain 设为顶级域(.example.com),path 设为 /,并在 HTTPS 下加上 Secure;同时强烈建议给会话 Cookie 加 SameSite=None,否则第三方上下文(比如前端跑在 cdn 子域)会被浏览器拒收。老版本 PHP/浏览器对 SameSite=None 的兼容性要留意,必要时通过 header 显式设置。
第四,X-Forwarded-* 与真实来源。反代后端时,应用拿不到真实 Host/Proto,生成的回调地址、CSRF 校验可能错位。nginx 要正确传递 X-Forwarded-Proto、X-Forwarded-Host,后端据此判断是否启用 HTTPS,并决定生成的绝对链接。否则你会遇到“登录跳回 HTTP,Cookie 再次被丢”的鬼畜问题。
第五,CSRF 与 CORS 不互替。很多人开了 CORS 就关了 CSRF,或者反过来。两者解决的是不同维度的问题:CORS 是浏览器层的跨域读写许可,CSRF 是跨站请求伪造防护。对于携带凭证的跨域 POST,依然需要 CSRF Token 或者同源双重校验(Origin/Referer 校验)。xiuno 的表单 Token 记得在 AJAX 接口也统一校验,并在跨域预检里把自定义头列入 Allow-Headers。
第六,上传与进度接口的特殊性。上传通常是 multipart/form-data,预检可能带来额外复杂度。建议统一通过 fetch/XHR withCredentials 发起,服务端针对 upload 路径单独返回 CORS 头,且把最大体积、超时与缓存头设置好。进度轮询接口如果频繁,最好允许 GET 缓存控制并缩短预检缓存时间(Access-Control-Max-Age 合理设置),降低开销。
第七,调试方法论。别拍脑袋改配置,先抓包看请求链:浏览器网络面板里逐一核对 Request Method、Origin、Credentials、Response 的四大头是否成对匹配;预检返回码必须是 200/204,不能 3xx;正式响应如果是 302,被浏览器当失败处理。再用 curl -X OPTIONS 还原预检,确认服务端行为与预期一致。
第八,环境分层。开发、预发布、生产尽量保持相同的域名拓扑(哪怕是 dev 子域),否则“开发没问题,线上炸锅”。把 CORS 头下沉到网关/边缘更可控,但涉及会话 Cookie 的细节仍需在应用层兜底。写一份白名单配置文件,代码里只读不写,避免临时改动把门敞开。
最后给一个可落地的最小策略:
- 网关按白名单精确回显 Origin,并加 Allow-Credentials:true;
- 允许的方法与头字段最小化列举,设置 Max-Age 缓存预检;
- 会话 Cookie 统一 SameSite=None; Secure; HttpOnly; domain=.example.com; path=/;
- 后端对携带凭证的写操作继续做 CSRF/Origin 校验;
- 日志中把 Origin、Access-Control-* 与会话 ID 打点,便于回溯。
跨域不是洪水猛兽,关键是把“浏览器策略、反代配置、会话机制”三件事对齐。配置一旦跑通,记得固化为模板,后面新模块直接套用,团队效率能提高一大截。 |
|