在 Web 前后端分离架构模式下,跨域(跨源)请求属于日常的基本情况了。浏览器出于安全考虑,会限制 JavaScript(简称 JS)脚本内发起跨源 HTTP 请求,同源没有此类限制。前端解决跨域方法有很多,比如 WebSocket 协议跨域、JSONP 请求跨域和跨域资源共享 CORS 等。
01
CORS 简介
CORS 全称为 Cross-Origin Resource Sharing,被译为跨域资源共享,简称跨域访问,是 W3C 制定的标准协议。它由一系列传输的 HTTP 标头(首部字段)组成,浏览器会根据这些 HTTP 标头决定着是否阻止前端 JS 代码获取跨域请求的资源。CORS 主要作用是消除各种 API 的同源限制,以便在不同源(服务器)之间共享资源,且确保跨域数据传输的安全性。
CORS 请求并不是一种特殊的 HTTP 请求,同样基于 HTTP 通信协议。CORS 请求默认携带”origin“标头,用于向目标网站指明请求的来源。origin 字段由三部分组成:协议、主机和端口,以下三种语法都是正确的。
origin: null
origin: <scheme>://<hostname>
origin: <scheme>://<hostname>:<port>
02
推荐一个查询浏览器特性、兼容性以及兼容到具体哪个版本的网站。例如查询各浏览器对 CORS 的支持情况,访问 URL 地址 https://caniuse.com/?search=CORS。如下图所示:
03
同源策略是由 Netscape 提出的一个著名的安全策略,它是一种安全约定。目前,所有可支持 JS 的浏览器都会遵循这个策略。Ajax 是当代 Web 应用程序中获取服务器数据的核心技术,可以实现网页内容异步更新,Ajax 底层之 XMLHttpRequest 对象和 Fetch API 都遵循同源策略。同源策略也是浏览器基本的安全功能之一。
同源的定义:当两个 URL 使用的协议、域名(主机)和端口都相同的情况下,则称为两个 URL 同源,反之称两个 URL 不同源。下表整理了同源与不同源的 URL 示例说明:
URL A |
URL B |
结果 |
分析原因 |
同源 |
|||
同源 |
|||
同源 |
80 是 HTTP 协议默认端口 |
||
同源 |
443 是 HTTPS 协议默认端口 |
||
不同源 |
域名相同,协议不同 |
||
不同源 |
域名相同,端口不同 |
||
不同源 |
|||
不同源 |
|||
https://www.example.com |
https://39.105.183.157 |
不同源 |
域名与 IP 不同 |
https://www.example.com |
不同源 |
完全不同的域名 |
|
http://www.example.com |
不同源 |
完全不同的域名 |
04
本例中,Nginx 服务器开启了 HTTP/2 协议,因此在 HTTP/2 二进制编码之前,必须将 HTTP 标头名称转换为小写。若请求头、响应头中包含大写的字段名将被视为格式错误。
关键知识点:如果 CORS 跨域请求是这三种方法之一:GET、POST 或 HEAD,那么在 HTTP 响应头中并不需要指明 access–control-allow-methods 字段的值。
4.1 简单请求
什么是简单请求?如果满足下述所有条件,才会被认定为”简单请求”。请注意,对于”简单请求”浏览器不会发起 CORS 预检请求。
1、HTTP 请求方法是以下三种之一:
-
GET
-
POST
-
HEAD
2、除了浏览器自动添加的首部字段(例如:connection,user–agent、date、referer 等)和 fetch 规范中定义的禁止使用的首部字段,以及”proxy-“和”sec-“小写开头的首部字段。允许设置的首部字段集合为:
4、请求中的任意 XMLHttpRequest 对象均没有注册任何事件监听器;XMLHttpRequest 对象可以使用 XMLHttpRequest.upload 属性访问。
例如,请看一个 CORS 简单请求的例子,用户访问站点 https://tool.box3.cn,页面尝试跨域请求从 https://api.box3.cn 获取数据,发起跨域请求的 JS 代码如下所示:
const xhr = new XMLHttpRequest();
const url = 'https://api.box3.cn/example/simple';
xhr.open('GET', url);
xhr.send();
:method: GET
:authority: api.box3.cn
:scheme: https
:path: /example/simple
origin: https://tool.box3.cn
user-agent: Mozilla/5.0 ... ...
:status: 200 OK
server: nginx
date: Thu, 17 Nov 2022 02:35:49 GMT
content-type: application/json; charset=utf-8
content-length: 47
access-control-allow-origin: *
本例中,服务器返回的首部字段 access–control-allow-origin: * 表明,该资源可以被任意外部域访问或接受所有的请求源。
access-control-allow-origin: *
如果只希望服务器允许来自 https://www.example.com 的访问,该首部字段的内容如下:
access-control-allow-origin: https://www.example.com
关键知识点:当响应的是附带身份凭证的请求时(例如:Cookie),服务器必须明确 access–control-allow-origin 字段的值,而不能使用通配符“*”,否则浏览器的同源策略会阻止该请求,并在控制台抛出错误。
4.2 预检请求和实际请求
首先,当请求发生跨域行为,且非简单请求时,才会产生 CORS 预检请求(CORS-preflight request)。其次与”简单请求”不同的是,”预检请求”是由浏览器自动发起的一个额外的 OPTIONS 请求,以获知服务器是否授权后续的实际请求(例如:XHR 或 Fetch API 发起的 HTTP 跨域请求)。其次,OPTIONS 请求包含了两个重要的标头(首部字段)access–control–request-method 和 access-control-request–headers。
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.box3.cn/example/request');
xhr.setRequestHeader('box3-token', '111-222-333-444');
xhr.send();
如上代码使用 GET 请求从服务器获取数据,该请求包含了一个自定义的请求头(box3-token:111-222-333-444)。因为该字段名超出了”简单请求”的定义范围,所以浏览器自行判断出这是一个非简单请求,在”实际请求”发起之前,会先发起一个”预检请求”。
下面是浏览器与服务器首次交互的报文信息,包括预检请求头和预检响应头(备注:user–agent 省略了部分内容):
/* 预检请求头 */
:method: OPTIONS
:authority: api.box3.cn
:scheme: https
:path: /example/request
access-control-request-method: GET
access-control-request-headers: box3-token
origin: https://tool.box3.cn
user-agent: Mozilla/5.0 ... ...
/* 预检响应头 */
:status: 204 No Content
server: nginx
date: Thu, 17 Nov 2022 02:35:35 GMT
access-control-allow-headers: box3-token
access-control-allow-origin: *
access-control-request-headers 告知服务器实际请求携带的自定义标头,access-control-allow-headers 告知客户端已支持的所有自定义标头,多个值之间以逗号分隔。
一般而言,服务器会对 OPTIONS 请求的结果添加缓存时间。目的是,客户端减少了预检请求交互的时间,同时也减少了对服务器的压力。比如服务器在响应头中指定 access-control-max-age: 3600 表示该响应的有效时间为 3600 秒,也就是 1 小时。在这段时间内,浏览器不会对同一请求再次发起预检请求,而是直接发起实际情况。
:status: 204 No Content
server: nginx
date: Thu, 17 Nov 2022 02:35:35 GMT
access-control-allow-headers: box3-token
access-control-allow-origin: *
access-control-max-age: 3600
关键知识点:对于 OPTIONS 请求,合法的 HTTP 状态码,应该定义在 2xx 范围内。比如状态码设置为 200 或 204,都是正确的。
最后,待预检请求通过之后,浏览器再发送实际请求。下面是实际请求的请求头和响应头:
/* 实际请求的请求头 */
:method: GET
:authority: api.box3.cn
:scheme: https
:path: /example/request
box3-token: 111-222-333-444
origin: https://tool.box3.cn
user-agent: Mozilla/5.0 ... ...
/* 实际请求的响应头 */
:status: 200 OK
server: nginx
date: Thu, 17 Nov 2022 02:35:35 GMT
content-type: application/json; charset=utf-8
content-length: 45
access-control-allow-origin: *
4.3 简单请求和凭据
默认情况下,对于 XMLHttpRequest 或 Fetch API 发起的跨域请求,浏览器不会发送 Cookie 信息。若要携带 Cookie,以 XMLHttpRequest 对象为例,需要设置属性 withCredentials 的值为 true。
本例中,站点 https://tool.box3.cn 内的 JS 脚本向 https://api.box3.cn 发起了一个简单的 GET 跨域请求,并附带了身份凭证 Cookie。JS 示例代码如下:
const xhr = new XMLHttpRequest();
const url = 'https://api.box3.cn/example/simple_cookie';
xhr.open('GET', url);
xhr.withCredentials = true;
xhr.send();
下面是浏览器与服务器交互的报文信息之关键部分(备注:user-agent 省略了部分内容):
/* 简单请求的请求头 */
:method: GET
:authority: api.box3.cn
:path: /example/simple_cookie
:scheme: https
cookie: access-token=100;
origin: https://tool.box3.cn
user-agent: Mozilla/5.0 ... ...
/* 简单请求的响应头 */
:status: 200 OK
server: nginx
date: Thu, 17 Nov 2022 02:52:07 GMT
content-type: application/json; charset=utf-8
content-length: 45
access-control-allow-credentials: true
access-control-allow-origin: https://tool.box3.cn
关键知识点:服务器在响应头中必须指定 access-control-allow-credentials: true 来表明跨域请求允许携带 Cookie,否则仍然会被浏览器的 CORS 策略阻止。2、服务器在响应头中必须指定 access-control-allow-origin 字段特定的域,该标头的值不能设置为通配符 “*”,否则仍然会被浏览器的 CORS 策略阻止。
4.4 预检请求和凭据
首先,一个完整的 CORS 预检请求,是由浏览器自动完成的,这个动作对用户是无感知的。
其次,与”简单请求和凭据”这小节整理的 CORS 策略知识点是一致的。那意味着,在 OPTIONS 请求的响应头中必须明确指定 access-control-allow-credentials: true 和 access-control-allow-origin 字段特定的域,否则后续的实际请求仍然会被浏览器的 CORS 策略阻止。
最后,在实际请求的响应头中,也需要明确指定这两个字段且保持与 OPTIONS 相同的值。
关键知识点:如果实际请求的 HTTP 方法,非 GET、POST 或 HEAD,那么 access-control-allow-methods 字段的值不能设置为通配符“*”,应设置为特定的 HTTP 请求方法名称,多个值之间以逗号分隔。
4.5 预检请求与重定向
回顾 4.2 小节的关键知识点,预检请求指的是 OPTIONS 请求,且 HTTP 状态码定义在 2xx 范围内。因此,如果一个预检请求发生了重定向,那么 HTTP 状态码一定大于 2xx,大多数浏览器将报告如下错误:
Access to XMLHttpRequest at 'https://api.box3.cn/example/request_redirect' from origin 'https://tool.box3.cn' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
有两种方式可以规避上述报错行为:
有两种方式可以规避上述报错行为:
1、在服务端上去掉对预检请求的重定向。
2、将该请求优化成一个简单请求。
05
常见的 4 种 CORS 错误
常见的 CORS 跨域请求错误,可能有以下 4 种情况(以下首部字段在服务器上配置):
1、受信来源 access-control-allow-origin 配置不正确。
2、受信的 HTTP 方法 access-control-allow-methods 配置不全。
3、受信的首部字段 access-control-allow-headers 配置不全。
4、access-control-allow-credentials 服务器与请求方之间的凭证许可配置错误。
06
借助浏览器找错误
引发 CORS 错误的原因是跨域请求失败导致,并非 JS 代码层面出现的逻辑性 BUG。如果 JS 发起的 HTTP 请求产生 CORS 错误,在 JS 代码层面无法获知具体是哪里出了问题,但是您可通过浏览器控制台获悉错误信息。例如在 Chrome 浏览器中,通过 F12 键启动开发者调试工具,在 Network 面板中了解具体的报错信息。如下图所示:
07
认识这些 HTTP 请求头和响应头
HTTP 请求头字段
Header |
说明 |
origin |
|
access-control-request-method |
出现于预检请求中,其作用是,通知服务器在实际请求中采用哪种 HTTP 方法。 |
access-control-request-headers |
HTTP 响应头字段
Header | 说明 |
access-control-allow-origin |
指定请求的资源能共享给哪些域。该字段只能指定一个来源。对于不需要携带身份凭证的请求,可以设置为通配符 *,表示允许所有来源访问。 |
在跨源访问时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到一些最基本的响应头。如果需要获取其他响应头,通过该字段添加白名单。 |
|
access-control-allow-methods |
|
access-control-allow-headers |
对于预检请求的响应,指明实际请求允许携带哪些 HTTP 头。 |
access-control-max-age |
指定预检请求的有效期,单位是秒。目的是减少发起预检请求的次数。 |
access-control-allow-credentials |
当设置为 true 时,告诉浏览器将响应公开给前端 JavaScript 代码。请注意,该值严格区分大小写,正确的写法是全小写。 |
原文地址:https://blog.csdn.net/qihoo_tech/article/details/128027373
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_9963.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!