欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

跨域

程序员文章站 2022-07-10 18:03:45
...

什么是跨域?

所谓跨域,就是网站的协议名(protocol:如http://)、域名(host:如 www.example.com)、端口号(port:例如80,默认端口可以省略)这三者中任意一个不同,网站之间的数据传输或者请求就属于跨域请求了。
这是由于浏览器的同源策略,为了防范跨站脚本的攻击,禁止客户端对不同源的服务器进行跨站调用,但是跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发送,但是返回结果被浏览器拦截了。
如果是非同源,共有三种行为受到限制:

  • cookie、localStorage和IndexedDB无法读取
  • DOM无法获得
  • AJAX请求不能发送

既然我们知道了什么是跨域,在实际的开发过程中我们又会无法避免跨域请求,那么怎么解决这个限制呢,其中有一个方法就是CORS。

CORS 跨域资源共享

CORS (Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。
背后的思想是使用自定义的http头部,让服务器能声明哪些来源可以通过浏览器访问该服务器上的资源,从而决定请求或相应是应该成功还是失败。
接下来我们介绍CORS的内部机制

1. 简介

CORS需要浏览器和服务器同时支持。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与,对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。
实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以实现跨域通信。

2. 两种请求

浏览器将CORS分成两类:简单请求(simple request)和非简单请求(not-so-simple request),只要同时满足以下两大条件(日常开发一般只关注这两点,所以这里只说这两点),就属于简单请求

  1. 请求方法是这三种之一:HEADGETPOST
  2. 只使用了如下的安全首部字段,不得人为设置其他首部字段
  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type :仅限于text/plainmultipart/form-dataapplication/x-www-form-urlencoded三种
  • HTML 头部 header filed 字段:DPRDownloadSave-DataViewport-WidthWidth

凡是不同时满足以上条件,就属于非简单请求

3.简单请求

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。例如:

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

在上面这段头信息中,Origin字段表示本次请求来自哪个源。如果这个源不在服务器许可范围内,会返回一个正常http回应(不包含Access-Control-Allow-Origin字段),浏览器接收后发现没有这个字段,就会抛出错误(这个错误无法通过状态码识别,因为http回应中的状态码有可能是200)。
如果这个源在许可范围内,服务器返回的响应,会多几个头信息字段:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

在这个头信息中,有三个与CORS请求相关的字段,都以Access-Control-开头。
3.1 Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
3.2 Access-Control-Allow-Credentials
该字段可选,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中,如果服务器明确许可客户端发送Cookie,就要把这个值设为true;如果不许可,就不要添加这个字段。(因为这个字段的值只能被设为true)
3.3 Access-Control-Expose-Headers
这个字段可选,这个值表示了XMLHttpRequest对象的getResponseHeader()方法能拿到的某个字段,因为这个方法本来是只能拿到6个基本字段(Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma)。有了上面这个响应值,getResponseHeader('FooBar')可以返回FooBar字段的值。
withCredentials 属性
如果要把cookie发到服务器,除了3.2中的字段值的设置表示服务器许可,还要开发者必须在 AJAX请求中打开withCredentials 属性

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

如果不想要浏览器发送cookie,就要设置这个属性值为false。
注意:
如果要发送cookie,3.1的字段值就不能设置为 * 号,必须指定明确的、与网页请求一致的域。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

4. 非简单请求

4.1 预检请求
非简单请求是那种需要对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json。这种会在正式通信之前,增加一次HTTP查询请求,称为“预检请求(preflight)”。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段
比如对于下面这样一个非简单请求

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

浏览器会自动发出一个预检请求,预检请求必须使用OPTIONS方法,用来询问,例如对应上面这个非简单请求的预检请求:

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

除了Origin字段,"预检"请求的头信息包括两个特殊字段:
(1). Access-Control-Request-Method
必须字段,列出浏览器的CORS请求会用到哪些HTTP方法,这里是PUT
(2). Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,这里是X-Custom-Header

4.2 预检请求的回应
服务器收到请求后,检查了楼上三个字段,确认允许跨域请求,就可以做出回应:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

这里面关键是的Access-Control-Allow-Origin字段,同3.1
(1). Access-Control-Allow-Methods
该字段必需,表明服务器支持的所有跨域请求的方法,而不单是浏览器请求的那个方法,这是为了避免多次"预检"请求。
(2). Access-Control-Allow-Headers
如果预检请求中有这个字段,则响应中这个字段是必须的,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
(3). Access-Control-Allow-Credentials
3.2
(4). Access-Control-Max-Age
可选,用来指定本次预检请求的有效期,单位为秒,在此期间,不用发出另一条预检请求。

如果预检请求被否定,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段,浏览器就知道预检请求被否定了。

4.3 浏览器的正常请求和回应
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。


5.完整请求流程

跨域
http://www.ruanyifeng.com/blog/2016/04/cors.html