Loading

轻松理解CORS协议规范

1 什么是CORS?

CORS是Cross-Origin Resource Sharing的缩写,意思是跨域资源共享

本质上,CORS是HTTP协议对浏览器不同网站AJAX请求的规范和限制。

Web世界里有无数个网站,每个网站都有自己的”门牌号“:协议://域名:端口

网站是部署在服务器某个端口上的应用进程,通过监听端口来接收外界的访问。外界通过域名(IP地址)来找到对应服务器,通过协议/端口来找到对应应用进程。

因此,协议/域名/端口可以定位网站在哪一个服务器,哪一个端口上。这三个只要有一个不同,说明该应用程序是不同的进程,就是不同的网站。

而跨域指的就是跨越网站。

当网站A想要通过AJAX请求获取网站B的数据(资源)时,就会发生跨域请求。因为它们是不同进程,不能保证对方的安全性。
如同陌生人想去你家拿东西或者放东西,楼下保安必须校验陌生人的身份信息。
浏览器就是Web世界里的保安,它根据CORS协议来核实AJAX请求是否被目标网站所允许。

2 如何辨别跨域请求?

在CORS预处理阶段,浏览器会为每个AJAX请求添加以下请求头:

  • Host:目标网站的“门牌号”
  • Origin:当前网站的“门牌号”

例如,我们在https://www.baidu.com/网站打开控制台,输入以下代码:

fetch("http://localhost:8080")

这次请求的两个请求头为:

Host: localhost:8080
Origin: https://www.baidu.com

HostOrigin不一致时,浏览器就判断这个是跨域请求,它就会根据CORS协议采取一定的校验措施。

3 简单请求&复杂请求&实际请求&预检请求

3.1 简单请求

出于性能和安全的考虑,浏览器将用户的CORS请求分为简单请求和复杂请求两种类型,分别进行不同的校验流程。
满足以下条件的为简单请求:

  • 请求方法为:GETHEADPOST
  • 请求头为:
    • 浏览器用户代理自动设置的请求头:ConnectionUser-Agent
    • forbidden header nameContent-LengthCookie
    • CORS-safelisted request-headeracceptaccept-language
    • Content-Type值为:text/plainmultipart/form-dataapplication/x-www-form-urlencoded
      对于简单请求,浏览器会直接将该请求发送给服务器:

3.2 复杂请求

不满足以上简单请求条件的就是复杂请求,比如:

  • 请求头Content-Typeapplication/json
  • 自定义请求头

对于复杂请求,浏览器会先发送一个轻量级的预检请求,询问目标网站是否允许访问。如果允许,才会发送实际请求:

理论上,浏览器会为复杂请求发送两次请求。但实际各浏览器的实现有所不同,有些浏览器对待复杂请求和简单请求一样,只发送一次请求;有些浏览器虽然会发送预检请求,但不会检测预检请求结果,始终会发送第二次实际请求。

3.3 预检请求

在发送复杂请求前,浏览器会先发送一个轻量级的预检请求,询问网站是否允许访问。

预检请求的请求方法是OPTIONS,它在请求头中携带实际请求的请求方法和请求头等信息,而不会携带实际请求的数据:

  • Origin:当前网站的“门牌号”
  • Access-Control-Request-Method:实际请求的请求方法,如POST
  • Access-Control-Request-Headers:实际请求的请求头,如Content-Type等,多个用,分隔

预检请求实际上是在询问服务器:“来自origin,携带Access-Control-Request-Headers请求头,请求方法是Access-Control-Request-Method的请求能不能访问?”。

3.4 实际请求

从服务器的角度来看,简单请求和复杂请求没有区别,都是会调用服务接口的实际请求。

而预检请求会在拦截器或预处理阶段就被系统自动处理,并不会调用到实际接口。

4 CORS预处理&校验

4.1 预处理

在发送跨域请求之前,浏览器会对请求进行一些处理,这样服务器才能判断这个请求是不是跨域请求,需不需要进行跨域处理。

对于简单请求和复杂请求,除了基本的请求方法和请求头,会额外添加标识当前网站地址的请求头:

  • Origin:当前网站的协议://域名:端口

对于预检请求(OPTIONS方法),还会添加标识后续复杂请求信息的请求头:

  • Origin:当前网站的“门牌号”
  • Access-Control-Request-Method:实际请求的请求方法,如POST
  • Access-Control-Request-Headers:实际请求的请求头,如Content-Type等,多个用,分隔

4.2 服务器校验

服务器如果有配置跨域,会按照以下几个方面对跨域请求进行处理:

  1. 校验是否是跨域请求:Origin请求头与服务器进程的协议://域名:端口是否一致
  2. 如果是跨域实际请求,会添加以下跨域响应头(对配置值进行过滤):
    1. Access-Control-Allow-Origin:允许访问的浏览器网站地址
    2. Access-Control-Expose-Headers:允许浏览器代码访问指定响应头
    3. Access-Control-Allow-Credentials:是否允许携带cookie
  3. 如果是跨域预检请求,除了上述响应头,会额外添加以下响应头(对配置值进行过滤):
    1. Access-Control-Allow-Methods:所允许的后续复杂请求的请求方法
    2. Access-Control-Allow-Headers:所允许的后续复杂请求的请求头
    3. Access-Control-Max-Age:后续复杂请求的有效请求时间

4.3 浏览器校验

浏览器接收到跨域响应后,会对上述响应头进行校验,判断是否允许跨域。

比如,浏览器会判断是否存在Access-Control-Allow-Origin响应头,该响应头与当前浏览器地址是否匹配。

如果跨域校验通过,浏览器会正常展示和响应服务器的结果给网站脚本。

如果跨域校验不通过,浏览器会隐藏服务器的结果,并且提示跨域请求失败。

5 个人思考

本质上来看,CORS并不是很神秘的东西。它只是针对浏览器跨网站请求的限制和规范。

CORS有着相对固定的业务请求流程,在这个流程下,每个浏览器的实现可能不同。

但是,浏览器必须做到两件事:

  1. 在发送跨域请求前进行预处理。
  2. 在收到跨域响应后进行校验。

这两件事对开发人员是无感的。我们只需要根据CORS的相关规范,在响应时添加必要的响应头,就可以完成对CORS的使用。

posted @ 2023-01-05 10:10  Xianuii  阅读(318)  评论(0编辑  收藏  举报