透彻理解跨域资源共享(CORS)
1. 什么是源?
源(Origin)
概念
跨域资源共享英文全称为"Cross-Origin Resource sharing",在介绍它之前先要搞懂其中的"Origin"是什么意思。
Origin译为源,指的是网页内容的来源,我们通过访问某个URL获取到文字、图片等资源,这个URL就是获取这些资源的源。
Origin实际上由三部分组成:
- schema(协议): 如http/https
- hostname(域名/地址): www.baidu.com
- port(端口): 如80/443
只有协议、主机名/域名、端口完全一致,我们才称之为同源。
示例
下面两个同源,因为它们有相同的协议(http)、主机名(example.com)、端口(并没有指定端口但默认都是80端口)。具体的文件路径不同,这不影响同源:
http://example.com/app1/index.html
http://example.com/app2/index.html
下面两个同源(默认通过80端口访问):
http://example.com:80
http://example.com
下面两个不同源,因为端口不同:
http://example.com
http://example.com:8080
下面几个不同源,因为域名不同:
http://example.com
http://example.org
http://www.example.com
http://myapp.example.com
2. 什么是跨源?
跨源(Cross-Origin)
“Cross-Origin"常被翻译为"跨域”,这样翻译有歧义,因为域、域名、源是不同的名词,互相之间还存在关系:比如域名(domain)就是源(origin)的组成部分之一,因此我更倾向于翻译为跨源而不是跨域。
最常见的跨源场景是前后端分离的项目:
- 用户访问
http//frontend.com
从前端服务器请求如html、js等静态资源。 - 用户获取到的js脚本中可能包含
fetch('http//backend.com')
用于向后端服务器请求数据。 - 用户点击页面中的某个按钮,触发js代码,请求开始发送。
- 用户本来是从
http//frontend.com
获取资源,现在又要向http//backend.com
发送请求,这个行为就是跨源请求行为。
3. 什么是同源策略?
同源策略(Same-origin policy)
同源策略是浏览器的一种安全策略,它限制了脚本从不同的源进行资源加载,这助于隔离潜在的危险,减少可能的攻击途径。例如,互联网上的恶意网站在用户的浏览器中运行JavaScript脚本向其它服务器发送请求。
你可以尝试随便打开一个不属于https://www.csdn.net/
的网站,在控制台执行fetch('https://www.csdn.net/')
,你会看到下面的错误:
Access to fetch at 'https://www.csdn.net/' from origin 'https://cn.vitejs.dev' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
对于前后端不分离的项目来说,静态资源和数据库中的数据都是来自同一台服务器,基本不存在跨源行为。而对于前后端分离的项目来说,静态资源在前端服务器,业务数据在后端服务器,所以一定会存在跨源请求,然而浏览器会限制这种行为,只是我们需要解决的问题。
4. 什么是跨域资源共享?
跨域资源共享(Cross-Origin Resource sharing)
下面翻译为"跨源资源共享"
概念
为了跨源,我们需要使用跨源资源共享(CORS),CORS是HTTP协议的一部分,是一种基于HTTP请求头的机制,它允许服务器指明哪些自身之外的源可以来请求它的数据。
跨源资源共享的工作原理就是添加一条HTTP请求头,让服务器明确告诉浏览器客户端哪些源可以访问它。此外那些可能对服务器数据产生副作用的HTTP请求方法(比如UPDATE),规范还要求浏览器向服务发送一个预检请求(使用OPTIONS方法),该请求将在正式发送请求之前,向服务器查询支不支持即将到来的请求方法,得到服务器明确的"批准"之后,再发送实际的内容。
简单请求的定义
不是所有请求都会触发上面所述的预检请求。不会触发预检请求的方法,我们一般称之为简单请求,简单请求要满足下面的条件:
- 只能使用如下方法:
GET
HEAD
POST
- 自定义的请求头(除了自动设置的请求头)只能包含:
Accept
Accept-Language
Content-Type
(还有额外要求)Range
- Content-Type只能设置为:
application/x-www-form-urlencoded
multipart/form-data
text/plain
不满足上述条件就不是简单请求,就会触发预检机制,也就是在正式请求之前使用OPTIONS
方法发送一条预检请求。
具体示例
- 简单请求
用户浏览器客户端通过前端服务器http//frontend.com
获取到JavaScript脚本,该脚本向后端服务器http//backend.com
使用GET方法请求Json数据:
const fetchPromise = fetch("http//backend.com");
fetchPromise
.then((response) => response.json())
.then((data) => {
console.log(data);
});
具体发送的HTTP请求内容如下:
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http//frontend.com
此时可以看到Origin
请求头被浏览器自动设置为http//frontend.com
,我们看看后端如何响应:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…XML Data…]
后端返回响应,响应头中包含了一条Access-Control-Allow-Origin: *
,这表示后端告诉浏览器,所以的源都可以访问我。
通过Origin
和Access-Control-Allow-Origin
标头是实现跨域资源访问最简单和标准的做法。这里如果后端只希望http//frontend.com
这一个源可以访问它,它可以设置响应头为Access-Control-Allow-Origin: http//frontend.com
。
- 预检请求
与简单请求不同,对于预检请求来说,浏览器首先会使用OPTIONS
方法向后端(另一个源发)发送一个HTTP请求,以确认接下来的实际请求是否可以安全发送。
js脚本:
const fetchPromise = fetch("http//backend.com/doc", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "text/xml",
"X-PINGOTHER": "pingpong",
},
body: "<person><name>Arun</name></person>",
});
fetchPromise.then((response) => {
console.log(response.status);
});
注意请求中设置了一个非标准请求头X-PINGOTHER
,因此这不是一个简单请求。
当请求发送,浏览器客户端和后端服务器之间进行第一次交互:
OPTIONS /doc HTTP/1.1
Host: frontend.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://frontend.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,x-pingother
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: frontend.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
观察浏览器客户端发送的OPTIONS
请求的请求头,除了Origin
之外,还有如下两个字段:
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,x-pingother
相当于浏览器客户端问后端服务器:
- 你允许使用POST方法的请求吗?
- 你允许请求头中包含
content-type,x-pingother
吗?
观察后端服务器响应头中如下几个字段:
Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
相当于服务器回答说:
- 我允许
https://frontend.com
这个源访问我 - 我允许
POST,GET,OPTIONS
这三种请求方式 - 我允许请求头中包含
X-PINGOTHER, Content-Type
- 在之后
86400
秒也就是一天的时间中,此类请求无需再发送OPTIONS
预检了。
整个预检的流程走完,才是真正的请求:
POST /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://frontend.com/examples/preflightInvocation.html
Content-Length: 55
Origin: frontend.com
Pragma: no-cache
Cache-Control: no-cache
<person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://frontend.com
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some XML content]
总结
我们常说解决跨域问题,本质就是在请求的过程中加上一些请求头。前端的Origin
请求头是自动的,因此解决跨域主要是后端的工作。对于常见的后端框架来说只需要通过中间件,或者在有些框架中称之为依赖的东西,为响应自动加上CORS所需要的响应头,告诉客户端浏览器我允许的源、方法、请求头等信息,这也有利于保证我们的后端安全。
原文地址:https://blog.csdn.net/2201_75632987/article/details/142290315
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!