【全栈篇】跨域、跨站与同源策略

6 阅读4分钟

在《前端视角下的网络协议》系列中,我们已经聊过了性能、安全和实时性。但无论你的协议跑得有多快,只要你跨出了自己域名的“围墙”,就必须面对 Web 安全的基石——同源策略(Same-Origin Policy)

对于 8 年经验的全栈开发者来说,跨域(CORS)不应该只是通过搜索报错信息来解决的麻烦,而是一套精密的信任分发协议


一、 同源策略:Web 的“围墙”

同源策略规定:如果两个 URL 的 协议(Protocol)域名(Domain)端口(Port) 任何一个不同,它们就是不同源的。

  • 为什么要拦? 如果没有同源策略,你访问的一个恶意网站可以通过脚本直接读取你已经登录的银行网站的 DOM 或 Cookie,导致隐私全无。

  • 拦住了什么? 1. AJAX / Fetch 请求(这是最常见的跨域报错)。

    1. DOM 操作(无法通过 iframe 操作另一个域名的 DOM)。

    2. Cookie / LocalStorage 读取


二、 CORS:给围墙开一扇“梯子”

CORS(跨源资源共享) 是 W3C 标准,它允许服务器明确告知浏览器:“我信任来自域名 A 的请求,请放行”。

1. 简单请求与预检请求(Preflight)

浏览器将跨域请求分为两类:

  • 简单请求: 使用 GET/POST/HEAD,且 Header 仅限于基础字段(如 Accept, Content-Typetext/plain 等)。浏览器直接发出请求。
  • 非简单请求: 只要涉及 PUTDELETE 或自定义 Header(如 Authorization),浏览器就会先发送一个 OPTIONS 请求进行“探路”。

2. 关键的响应头

作为全栈开发者,在 Node.js 或 Nginx 层配置跨域时,核心就是这三个头:

  • Access-Control-Allow-Origin: 指定允许的域名(高并发生产环境严禁设为 * )。
  • Access-Control-Allow-Methods: 允许的 HTTP 动词。
  • Access-Control-Allow-Headers: 允许的自定义头字段。

三、 跨站(Cross-Site)与 CSRF 防御

很多开发者分不清“跨域”和“跨站”。简单来说,跨域看的是地址是否完全一致,跨站看的是顶级域名(eTLD+1)是否一致。

1. Cookie 的“隐形翅膀”

即便你跨域了,浏览器在发送请求时默认仍可能带上目标域名的 Cookie。这给了 CSRF(跨站请求伪造) 攻击可乘之机。

2. 终结者:SameSite 属性

现代浏览器引入了 SameSite 属性来治理跨站 Cookie 发送:

  • Strict: 只有在同站页面内发出的请求才带 Cookie。
  • Lax (现代浏览器默认值): 允许从第三方网站导航到本站时带 Cookie,但禁止跨站的 AJAX 带 Cookie。
  • None: 必须配合 Secure 使用,允许任何场景下发送。

四、 实战:优雅地解决跨域的三个方案

方案 A:后端配置(最佳实践)

在 Node.js (Express/Koa) 中使用中间件:

JavaScript

app.use(async (ctx, next) => {
  ctx.set('Access-Control-Allow-Origin', 'https://your-frontend.com');
  ctx.set('Access-Control-Allow-Credentials', 'true'); // 允许跨域携带 Cookie
  if (ctx.method === 'OPTIONS') { ctx.status = 204; } // 快速响应预检
  await next();
});

方案 B:反向代理(前端最爱)

在 Nginx 或开发环境(Vite/Webpack Proxy)中配置。

  • 原理: 浏览器访问同源的 /api,Nginx 在后台转发给跨域的真实服务器。对浏览器来说,这依然是“同源”的。

方案 C:不可忽视的“预检缓存”

如果你发现每个跨域接口都要发两次请求(OPTIONS + 实际请求),这会显著拖慢性能。

  • 优化: 设置 Access-Control-Max-Age: 86400。让浏览器在一天内缓存该接口的预检结果,减少网络往返。

💡 前端开发者的硬核贴士

  • 不要信任 Origin: * :如果你需要跨域携带 Cookie (withCredentials),Origin 必须是明确的域名,不能是星号。
  • CSRF 仍然存在:虽然有 SameSite,但在某些旧浏览器或特定场景下仍有风险。全栈开发中,关键写操作务必校验 CSRF Token 或自定义 Header(如 X-Requested-With)。
  • 本地开发痛点:如果你本地开发时被跨域卡住,不要去关浏览器的安全模式,应该通过 Vite 的 proxy 配置来解决。

结语

围墙(同源策略)是为了保护用户,梯子(CORS/Proxy)是为了方便开发。理解了协议里的每一项配置,你就不仅能在围墙内安全起舞,也能在复杂的全栈架构中自由穿梭。