CORS解决跨域问题
0. 背景
浏览器中,网站A的网络请求访问网站A的资源(图片,HTTP请求)是很顺畅的,而想访问网站B的资源,就要面对跨域资源访问的问题了。面对跨域问题,有很多的解决方案,本文讨论使用 CORS 来解决的方案。
本文结构
1. 什么是跨域问题,什么是同源策略
1.1 不同源则触发一个跨域的HTTP请求
1.2 同源策略
1.3 源
2. CORS 概述
3. CORS 的控制场景
3.1 简单请求
3.2 预检请求
3.3 附带携带身份凭据的请求
3.4 响应头的额外暴露字段
3.5 预检请求的缓存时长
1. 什么是跨域问题,什么是同源策略
跨域资源共享是由同源策略引发的,首先要了解同源策略。而要了解同源策略先要了解什么是“源”,下面我们层层展开。
1.1 不同源则触发一个跨域的HTTP请求:
在浏览器中,当 “一个资源” 向 “与它所在的服务器不同的域、协议或端口” 请求一个资源时,该资源会发起一个跨域 HTTP 请求。
浏览器可能“限制发起跨域请求",或者是 “可以发起跨域请求,但是返回结果被浏览器拦截
”。
出于安全原因,浏览器限制
跨源HTTP请求。这意味着使用 Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。
1.2 同源策略
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
也就是说,如果“源”相同,则运行访问。如果不同,则被限制。我们继续了解下什么是源。
1.3 源
Web内容的源由它的URL的 协议,主机(域名)和端口定义。
只有当协议,主机和端口都匹配时,两个对象被认为具有相同的起源。而可以使用 CORS 解除这个限制。
源由三部分组成:
- 协议
- 主机(域名)
- 端口
同源的例子
网址 | 说明 |
---|---|
http://example.com/app2/index.html ,http://example.com/app1/index.html | 同源,以为都是http和域名相同 |
http://Example.com:80 ,http://example.com | 同源,虽然写80端口,单实际上80是默认端口(可以省略) |
不同源的例子
网址 | 说明 |
---|---|
http://example.com/app1 ,https://example.com/app2 | 不同源,因为不同的协议: http 对比 https |
http://example.com , http://www.example.com , http://myapp.example.com | 不同源,因为不同的主机名 |
http://example.com , http://example.com:8080 | 不同源,因为不同的端口号。 |
浏览器的同源策略提升了安全性,然而在业务需求中仍然需要需要“访问不同源的资源”,于是提出了“CORS机制”。
现代浏览器支持使用 CORS,以降低跨域 HTTP 请求所带来的风险。CORS 机制允许 Web应用 进行跨域访问控制,从而使跨域数据传输得以安全进行。
2. CORS 概述
跨域资源共享 CORS 是一种机制,它使用额外的 HTTP头 来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的资源。
CORS 使用额外的请求头来说明访问是被允许的
跨域资源请求分为:
- (1)服务器通过请求头来声明“允许的源站,和允许的资源”
- (2)预检请求
- (3)携带身份凭据(cookie等)的情形
跨域资源共享标准新增了一组 HTTP 请求头字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。
对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
CORS请求失败会产生错误,但是为了安全,在JavaScript代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。
3. CORS 的控制场景
下面分几个场景来说明。
3.1 简单请求
简单请求不会触发 CORS 预检请求。若请求满足所有下述条件,则该请求可视为“简单请求”:
使用下列方法之一:
GET
HEAD
POST
HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Content-Type 的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
交互流程
(1)请求端:
当发起一个跨域请求时,浏览器会自动在请求头中加入 Origin 字段,它是发起方所处于的域,表明了“来源”。
示例:请求中含有
Origin: http://foo.example
(2)服务端:
服务端根据“来源” 来决定处理方式,如果同意,则返回的消息头中添加 Access-Control-Allow-Origin 字段。这个字段的值可以是“ * ”(表示任意的域名都允许),或者是具体的域名地址。
Access-Control-Allow-Origin: *
简单请求的跨域,通过 Access-Control-Allow-Origin 请求头的处理。 如果在 请求头中 包含了特殊自定义内容,就需要 预检请求 了。
3.2 预检请求(preflight request)
“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。
"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
当请求满足下述任一条件时,即应首先发送预检请求:
(1)使用了下面任一 HTTP 方法:
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
(2)Content-Type 的值不属于下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
(3)请求头中包含的自定义请求头
比如含有 Authorization, token 作为授权的字段
交互流程
image.png示例假设:
假设我们自定义了一个 请求头字段 “X-PINGOTHER” , 后续将在请求中携带这个请求头字段。
(1) 请求端:
先发一个 OPTION 的预检请求,内容有:
Origin 说明了来源
Access-Control-Request-Method 说明 下次将正式采用的方法。Access-Control-Request-Headers 说明将采用的自定义header 字段名。
示例:
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
(2) 服务端:
服务收到上面的请求后,根据自身情况来判定是否接收和处理。如果同意接受,则返回的 响应中包含下面几个请求头。
Access-Control-Allow-Origin 说明了支持跨域的来源
Access-Control-Allow-Methods 说明了支持的跨域方法
Access-Control-Allow-Headers 说明了 将接受的自定义header字段名
Access-Control-Max-Age说明了 预检请求的结果能够被缓存多久,即在多久内可以省略 预检请求 。
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
至此,完成了 预检。
(3) 请求端
预检请求完成之后,发送实际请求,在这里 假设的自定义请求头字段 X-PINGOTHER ,就会被放在请求头中了。示例:
X-PINGOTHER: pingpong
Origin: http://foo.example
(4) 服务端:
服务端 根据实际情况处理请求,仍然要返回 Access-Control-Allow-Origin 声明。
Access-Control-Allow-Origin: http://foo.example
是否需要发送 预检请求,是浏览器根据规则自动做出判断。预检的过程和头部字段也是浏览器自动处理。如果在这个过程中发生了“拒绝”,那么,在发送预检请求后,就没后后续了,浏览器会 “不再发送实际的请求”,或者 “丢失实际请求中的响应”。
3.3 附带携带身份凭据的请求
对于跨域 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。
(1)请求端
在请求端中的 withCredentials 属性则告诉浏览器“ 是否自动在请求中携带 cookie 的值 ”
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
(2)服务端
服务端的请求头中的 Access-Control-Allow-Credentials 说明了是否接受凭据信息(比如cookie)。
Access-Control-Allow-Credentials: true
如果 请求端包含了 withCredentials ,而服务端未包含 Access-Control-Allow-Credentials,那么浏览器将丢失 这次 服务端的响应内容,而不传递给请求的发送者。
附带身份凭证的请求与通配符
对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“”。
这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“”,请求将会失败
3.4 响应头的额外暴露字段
服务端通过响应头中的字段 Access-Control-Expose-Headers 来说明额外暴露字段。
CORS请求时,一般只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
3.5 预检请求的缓存时长
Access-Control-Max-Age 头指定了预检请求的结果能够被缓存多久
Access-Control-Max-Age: <delta-seconds>
参数 delta-seconds 表示 预检请求的结果在多少秒内有效。
END