跨域

很多人只知道“前后端分离会遇到跨域”,却不了解浏览器到底在保护什么。本文从同源策略和 CSRF 风险入手,系统说明 CORS 的设计初衷、请求分类、关键请求头和响应头,并总结服务端直接配置 CORS、NGINX 代理、本地开发代理等常见实践,让你真正理解跨域问题的本质,而不仅仅是“会复制一段配置”。

post

Vitah Lin

3 min read
0

/

什么是跨域

跨域访问是指请求一个与自身资源不同源(不同的域名、协议或端口)的资源。不同源可以是不同的域名、协议或端口。

跨域(CORS)不是协议,不是语言特性,不是前端后端技术差异

它的本质是浏览器为保护用户的 Cookie/Token/隐私而设置的一种安全策略(Same-Origin Policy 同源策略)。

  • 跨域只存在于浏览器环境中
  • 服务端 → 服务端 没有跨域问题
  • Curl、Postman、Node.js 全部不跨域

跨域访问是指请求一个与自身资源不同源(不同的域名、协议或端口)的资源。不同源可以是不同的域名、协议或端口。浏览器判断是否同源:协议(http/https)、域名、端口,只要有一个不同就属于跨域。

浏览器出于安全考虑设置了同源策略,限制了从脚本内发起跨域请求。但在实际应用中,经常会发生跨域访问。为此,W3C 提供了一个标准的跨域解决方案:跨域资源共享(Cross-Origin Resource Sharing,CORS),支持安全的跨域请求和数据传输。

浏览器将 CORS 请求分为以下两类:

  • 简单请求
  • 预检请求:防止资源被本来没有权限的请求修改的保护机制。浏览器会在发送实际请求之前使用 OPTIONS 方法发送一个预检请求,从而获知服务端是否允许该跨域请求。服务端确认允许后,才会发起实际的 HTTP 请求。

简单请求

请求满足如下所有条件,为简单请求:

  • 请求方法是如下之一:
    • HEAD
    • GET
    • POST
  • HTTP 头信息不超过以下几种字段:
    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type 的值仅限下列几种:
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded

预检请求

不符合简单请求条件的请求,会在正式通信之前触发一个 OPTIONS 请求进行预检,即浏览器要先问服务端:我可以访问吗,会不会有危险。

这时候就发一个OPTIONS请求,不带任何业务数据:OPTIONS /api/user。这类请求为预检请求。预检请求会在请求头中附带一些正式请求的信息给服务端,主要有:

  • Origin:请求源信息。
  • Access-Control-Request-Method:接下来的请求类型,如 POST、GET 等。
  • Access-Control-Request-Headers:接下来的请求中包含的用户显式设置的 Header 列表

服务端收到预检请求后,会根据上述附带的信息判断是否允许跨域,通过响应头返回对应的信息:

  • Access-Control-Allow-Origin:允许跨域的 Origin 列表。
  • Access-Control-Allow-Methods:允许跨域的方法列表。
  • Access-Control-Allow-Headers:允许跨域的 Header 列表,允许前端带哪些自定义header
  • Access-Control-Expose-Headers:允许暴露的 Header 列表。
  • Access-Control-Max-Age:最大的浏览器缓存时间,单位:秒。
  • Access-Control-Allow-Credentials:是否允许发送 Cookie。

浏览器会根据返回的 CORS 信息判断是否继续发送真实的请求。以上行为都是浏览器自动完成的,服务端只需要配置特定的 CORS 规则。

浏览器确认没问题后,再发真正的POST请求。

浏览器为什么阻止跨域请求

为了安全。因为用户访问 attacker.com,如果浏览器允许跨域,attacker.com 可以直接用用户在浏览器里的 cookie 去请求银行网站:GET https://bank.com/transfer?to=hacker&money=100000000

浏览器会自动带着用户的 cookie 发出去。这就叫 CSRF(跨站请求伪造),所以浏览器必须阻止跨域。

跨域处理方式

服务端设置CORS头

在API上设置:

Access-Control-Allow-Origin: https://你的前端域名

用NGINX反向代理

前端请求:/api ,NGINX 代理到 proxy_pass http://backend:8080;,这样浏览器看到的域名始终是前端域名 → 不跨域。

前端dev-server代理(仅开发环境使用)

前端在本地启动时,页面地址是:localhost:3000, 如果接口是 http://api.example.com 直接访问就会跨域。这时候 dev-server 启动了一个本地HTTP服务,代理规则告诉它:

所有 /api 请求转发到 http://api.example.com

浏览器只看到 localhost 域名,永远只看到同源请求,所以跨域问题被绕开。

这就是为什么在开发环境不会出现跨域问题的原因,使用 Next.js、Vite、Webpack DevServer 等开发环境时,通常会配置 proxy 或 rewrite,把请求转发到后端服务器。脚手架自动处理了。

如何减少浏览器预检次数(提升性能)

设置缓存:Access-Control-Max-Age: 86400,预检结果可以缓存一天。

静态资源为什么不跨域

如图片:<img src="https://image.xxx.com/a.png"> 不会报跨域,因为_浏览器对资源加载策略宽松(只不允许JS访问资源内容)。_例如:

  • 加载图片:可以显示,但 JS 不能读 pixel(除非 CORS)
  • 加载 script:默认允许执行
  • 加载字体:通常需要 CORS
  • 加载视频:限制更严格

这些都属于 CSP / CORS / Fetch API 的权限体系

Don't miss these

0
双因子认证-Google Authenticator工作原理

Vitah Lin

·5 min read