保持 跨域_浏览器和服务器实现跨域(CORS)判定的原理

本文聚焦前端跨域问题,先介绍同源策略,接着通过实验阐述浏览器和服务器对跨域的判定流程,即浏览器先按同源策略匹配,不同源则发跨域请求,服务器依配置返回文件头,浏览器再根据文件头判断。最后以Nginx为例,讲解服务器实现跨域传输的配置方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前端对Cross-Origin Resource Sharing 问题(CORS,中文又称'跨域')应该很熟悉了。众所周知出于安全的考虑,浏览器有个同源策略,对于不同源的站点之间的相互请求会做限制(跨域限制是浏览器行为,不是服务器行为。)。不过下午想到了一个略无趣的问题:浏览器和服务器到底是如何判定有没有跨域呢?本文主要分两个部分,一是对这个问题的总结,二是nginx下如何配置服务器允许跨域。
<!-- more -->

同源策略

同源指的是域名(或IP),协议,端口都相同,不同源的客户端脚本(javascript、ActionScript)在没明确授权的情况下,不能读写对方的资源。

同源的判定:
http://www.example.com/dir/page.html为例,以下表格指出了不同形式的链接是否与其同源:(原因里未申明不同的属性即说明其与例子里的原链接对应的属性相同)

d1f90934ab98fd16ead481a3316a09dc.png

是否允许跨域的判定

前文提到了同源策略的判定,然而同源策略在加强了安全的同时,对开发却是极大的不便利。因此开发者们又发明了很多办法来允许数据的跨域传输(常见的办法有JSONPCORS)。当域名不同源的时候,由于跨域实现的存在,浏览器不能直接根据域名来判定跨域限制。那么浏览器具体又是如何实现判定的呢?看下面的例子。

环境说明

  1. 参与实验的前端域名三个有:http://www.zhihu.comhttp://segmentfault.comhttp://localhost
  2. 请求的服务器端地址为http://localhost/city.json,服务器解析引擎使用的nginx,且服务器只配置了允许来自http://segmentfault.com的跨域请求
  3. 检测方法:在各个域名下利用chrome浏览器的console界面模拟发送ajax请求,代码如下:
    var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://localhost/city.json',true); xhr.send();

实验过程

  1. http://localhost域名下,请求成功。
    服务器回应的http文件头如下:
    HTTP/1.1 200 OK Server: nginx/1.6.2 Date: Sun, 05 Jul 2015 17:44:06 GMT Content-Type: application/octet-stream Content-Length: 2084 Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT Connection: keep-alive ETag: "5531f79c-824" Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET, POST, OPTIONS Accept-Ranges: bytes
  2. http://segmentfault.com域名下,请求成功
    服务器回应的http文件头如下:
    HTTP/1.1 200 OK Server: nginx/1.6.2 Date: Sun, 05 Jul 2015 18:17:27 GMT Content-Type: application/octet-stream Content-Length: 2084 Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT Connection: keep-alive ETag: "5531f79c-824" **Access-Control-Allow-origin: http://segmentfault.com** Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET, POST, OPTIONS Accept-Ranges: bytes
  3. http://www.zhihu.com下,请求失败虽然都是失败,但是返回的HTTP文件头内容会视服务器是否有配置跨域请求而发生变化

服务器允许跨域请求

(仅允许来自http://segmentfault.com的跨域请求)
console.log窗口提示:

XMLHttpRequest cannot load http://localhost/city.json. The 'Access-Control-Allow-Origin' header has a value 'http://segmentfault.com' that is not equal to the supplied origin. Origin 'http://www.zhihu.com' is therefore notallowed access.

服务器回应的http文件头如下:

HTTP/1.1 200 OK
    Server: nginx/1.6.2
    Date: Sun, 05 Jul 2015 17:59:25 GMT
    Content-Type: application/octet-stream
    Content-Length: 2084
    Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
    Connection: keep-alive
    ETag: "5531f79c-824"
    Access-Control-Allow-origin: http://segmentfault.com
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Accept-Ranges: bytes

服务器不允许任何跨域请求

console.log窗口提示:

XMLHttpRequest cannot load http://localhost/city.json. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://www.zhihu.com' is therefore not allowed access.

服务器回应的http文件头如下:

HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Sun, 05 Jul 2015 17:51:29 GMT
Content-Type: application/octet-stream
Content-Length: 2084
Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
Connection: keep-alive
ETag: "5531f79c-824"
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, OPTIONS
Accept-Ranges: bytes

跨域的判定流程

zhihu页面的两次浏览器报错以及segmentfault的成功返回值来看,可以很容易得出浏览器和服务器的合作判定步骤如下:

  1. 浏览器先根据同源策略对前端页面和后台交互地址做匹配,若同源,则直接发送数据请求;若不同源,则发送跨域请求。
  2. 服务器解析程序收到浏览器跨域请求后,根据自身配置返回对应文件头。若未配置过任何允许跨域,则文件头里不包含Access-Control-Allow-origin字段,若配置过域名,则返回Access-Control-Allow-origin+ 对应配置规则里的域名的方式
  3. 浏览器根据接受到的http文件头里的Access-Control-Allow-origin字段做匹配,若无该字段,说明不允许跨域;若有该字段,则对字段内容和当前域名做比对,如果同源,则说明可以跨域,浏览器发送该请求;若不同源,则说明该域名不可跨域,不发送请求

(但是不能仅仅根据服务器返回的文件头里是否包含Access-Control-Allow-origin来判断其是否允许跨域,因为服务器端配置多域名跨域的时候,也会出现不能跨域的域名返回包里没有Access-Control-Allow-origin字段的情况。下文配置说明里会讲。)

配置服务器实现跨域传输

前面讲到了同源策略的基本判定,以及浏览器实现跨域判断的方式,那么,如何在服务器端做配置来允许跨域传输呢?下文将以Nginx为例,讲一下三种情况下的配置。

配置项解析

CORS常用的配置项有以下几个:

  • Access-Control-Allow-Origin(必含) – 允许的域名,只能填通配符或者单域名
  • Access-Control-Allow-Methods(必含) – 这允许跨域请求的http方法(常见有POSTGETOPTIONS
  • Access-Control-Allow-Headers(当预请求中包含Access-Control-Request-Headers时必须包含) – 这是对预请求当中Access-Control-Request-Headers的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。
  • Access-Control-Allow-Credentials(可选) – 该项标志着请求当中是否包含cookies信息,只有一个可选值:true(必为小写)。如果不包含cookies,请略去该项,而不是填写false。这一项与XmlHttpRequest2对象当中的withCredentials属性应保持一致,即withCredentials为true时该项也为true;withCredentials为false时,省略该项不写。反之则导致请求失败。
  • Access-Control-Max-Age(可选) – 以秒为单位的缓存时间。预请求的的发送并非免费午餐,允许时应当尽可能缓存。

具体配置举例

全域名或者单域名允许跨域

这个最省心
打开Nginx的配置文件(默认为nginx.conf)。找到对应域名设置的local配置部分。
添加以下内容:

add_header 'Access-Control-Allow-origin' 'http://www.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
添加的域名必须带http://协议头(否则服务器无法区分是http还是https),如果接受所有域名的跨域请求,则可以用*(安全性有问题,不推荐)

添加多域名跨域配置

如果允许跨域的域名有多个但出于安全问题又不想配置全域名通配的时候,就可以用到nginx里的if判断了。
添加如下内容:

if ($http_origin = 'http://segmentfault.com' ) {  
 add_header 'Access-Control-Allow-Origin' "$http_origin";
 }
if ($http_origin = 'http://localhost:4000' ) {  
 add_header 'Access-Control-Allow-Origin' "$http_origin";
 }
 add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
  • 如果对正则比较熟悉的,可以直接用正则来匹配条件判断,不需要用if这么麻烦的方式。
  • 'Access-Control-Allow-Methods' 允许多参数,'Access-Control-Allow-origin'不允许多参数,所以只能是条件语句判断要不要加这个。这也是我前面提到的为什么即使HTTP文件头返回值里没有'Access-Control-Allow-origin',也不能说明它就是不允许跨域的。
  • nginx配置文件的http配置部分不能用if条件语句,所以多域名的时候必须加在local部分内。另外加在local内的只对对应的服务器域名做跨域请求的配置,加在http里会让跑在该nginx下的所有网站都统一采取这种配置。
  • Access-Control-Allow-Origin也可以改成全小写的形式,不影响结果.(access-control-allow-origin也可以)

PS:
这篇文章写的可能有点绕,另外由于运维方面和WEB安全方面不是特别熟悉,所以后面配置那里未必是最优解,恳请看到的各位指点。

<think>好的,用户现在想了解浏览器限制的原理以及服务器如何设置CORS规则。我需要结合之前的对话提供的引用来组织回答。 首先,回顾之前的回答,用户已经知道问题由同源策略引起,而CORS是解决方案之一。现在他们希望深入理解原理CORS的配置机制。根据引用[3][4],服务器需要在响应头中添加特定的CORS字段,比如Access-Control-Allow-Origin。同时,引用[4]提到了简单请求预检请求的区别,这一点需要详细解释。 用户的问题分为两部分:限制的原理CORS规则设置。先从同源策略开始,说明浏览器如何限制请求,包括协议、名、端口的不同情况。然后,详细解释CORS机制,包括简单请求预检请求的处理流程,以及服务器需要设置的响应头字段。需要引用[3][4]中的信息,比如Access-Control-Allow-Credentials的条件,以及预检请求中涉及的Access-Control-Allow-MethodsAccess-Control-Allow-Headers。 另外,用户可能对为什么需要预检请求不太清楚,需要举例说明哪些请求会触发预检,比如非简单方法或自定义头。同时,要提到CORS的安全机制,如携带Cookie时的限制,必须指定具体名而不是通配符,这点引用[3]中的内容。 最后,确保回答结构清晰,分点说明原理服务器设置步骤,并在相关部分添加正确的引用标记。检查是否有遗漏的关键点,比如简单请求的条件,以及服务器配置的具体示例,如SpringNginx的实现,参考引用[1][3]中的方法。 总结起来,先解释同源策略限制的表现,再详细说明CORS的工作机制,包括服务器如何通过响应头声明规则,最后总结常见配置方式,确保用户能全面理解原理实施方法。</think>#### 一、浏览器限制原理 **核心机制:同源策略(Same-Origin Policy)** 浏览器通过**同源策略**限制以下行为以保障安全[^1][^3]: 1. **访问控制** - 禁止读取资源:例如通过 `XMLHttpRequest` 或 `Fetch API` 请求不同源的接口数据。 - 限制操作 DOM:若页面通过 `iframe` 嵌入内容,脚本无法直接访问其 DOM。 2. **同源判定标准** URL 的 **协议(HTTP/HTTPS)、名(example.com)、端口(443/8080)** 必须完全相同。 **示例**: - `https://a.com` 与 `http://a.com`(协议不同) - `a.com` 与 `api.a.com`(子名不同) 3. **限制范围** - 接口请求(AJAX/Fetch) - Cookie、LocalStorage 读取 - Web Workers 脚本加载 - 字体文件(`@font-face`)加载 --- #### 二、服务器声明允许的机制:CORS 规则 CORS资源共享)通过 **HTTP 响应头** 向浏览器声明允许的规则[^3][^4],具体流程如下: ##### 1. **简单请求(Simple Request)** **触发条件**: - 请求方法为 `GET`/`POST`/`HEAD` - 请求头仅包含以下字段: `Accept`、`Accept-Language`、`Content-Language`、`Content-Type`(仅限 `text/plain`、`multipart/form-data`、`application/x-www-form-urlencoded`) **服务器响应头要求**: ```http Access-Control-Allow-Origin: https://your-site.com # 必须明确指定名,不能为* Access-Control-Allow-Credentials: true # 若需携带 Cookie[^3] ``` ##### 2. **预检请求(Preflight Request)** **触发条件**: - 使用 `PUT`/`DELETE` 等非常用方法 - 包含自定义请求头(如 `Authorization`) - `Content-Type` 为 `application/json` **流程**: 1. 浏览器自动发送 `OPTIONS` 预检请求,询问服务器是否允许。 2. 服务器需返回以下头部: ```http Access-Control-Allow-Origin: https://your-site.com Access-Control-Allow-Methods: GET, POST, PUT # 允许的方法 Access-Control-Allow-Headers: Content-Type, X-Custom-Header # 允许的请求头 Access-Control-Max-Age: 86400 # 预检结果缓存时间(秒) ``` ##### 3. **携带凭证(Cookie)的特殊要求** 若请求需要携带 Cookie,需满足: - 服务器设置 `Access-Control-Allow-Credentials: true` - 客户端设置 `withCredentials: true`(JavaScript 代码) - `Access-Control-Allow-Origin` **不能为通配符** `*`,必须指定具体名[^3][^4] --- #### 三、CORS 配置示例 ##### 1. **Spring 后端配置** ```java @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("https://your-site.com") .allowedMethods("GET", "POST", "PUT") .allowCredentials(true) .exposedHeaders("X-Custom-Header"); // 允许客户端读取的响应头[^4] } } ``` ##### 2. **Nginx 反向代理配置** ```nginx location /api/ { proxy_pass http://backend-server; add_header Access-Control-Allow-Origin "https://your-site.com"; add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; add_header Access-Control-Allow-Headers "Content-Type, Authorization"; add_header Access-Control-Allow-Credentials "true"; } ``` --- #### 四、关键区别总结 | 机制 | 简单请求 | 预检请求 | |---------------------|-----------------------------------|-----------------------------------| | 触发条件 | 方法/头符合限制 | 包含非常规方法或自定义头 | | 浏览器行为 | 直接发送请求 | 先发送 `OPTIONS` 预检请求 | | 服务器响应头 | `Access-Control-Allow-Origin` | 需声明方法、头、缓存时间等 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值