什么是跨域?
跨域简单来说就是浏览器禁止加载非同源情况下的脚本。浏览器的同源策略是为了保护用户的隐私安全,如果没有同源策略,很容易遭受网络攻击。
跨域策略限制的内容
- Cookie、LocalStorage、IndexedDB 等存储性内容
- DOM 节点
- AJAX Fetch请求
以下标签允许跨域加载资源:
<img src='' />
<a href=''></a>
<script src=''></script>
<form url=''/>
<iframe url=''/>
什么是同源策略?
所谓同源是指”协议+域名+端口”三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
常见的跨域场景
解决跨域的方法–CORS
CORS是跨域资源共享(Cross-origin resource sharing),只要服务端实现了CORS,就可以实现跨域资源共享,服务端通过Access-Control-Allow-Origin属性来实现跨域,该字段表示哪些域名可以访问资源,如果是*就表示所有的域名都可以访问该资源。
它允许浏览器向跨源服务器发出XMLHttpRequest、Fetch请求,从而克服了AJAX只能同源使用的限制。
CORS中,我们将请求分为简单请求和非简单请求,因为浏览器对于这两种请求的处理方式不同。
对于如何、为什么要区分简单/非简单请求,请参照我另一篇文章,对于为什么要区分简单/预检请求的理解
简单请求
当客户端发出简单跨域请求时,会在请求头中自动添加origin
字段,表示本次请求来自哪个源(协议 + 域名 + 端口)。服务端根据这个字段看是否允许此次请求。
如果origin指定的源,不在服务器允许的范围内,服务器会返回一个正常的http响应,浏览器发现响应头中没有Access-Control-Allow-Origin
字段,就会拦截该响应并报跨域的错误。
如果origin指定的源,在允许的范围内,请求的响应头会返回以下几个字段:
Access-Control-Allow-Origin: http://api.bob.com //必选
Access-Control-Allow-Credentials: true //可选
Access-Control-Expose-Headers: FooBar //可选
复制代码
Access-Control-Allow-Origin
字段是一定会有的,表示服务端允许访问该资源的源,为*
表示允许所有源访问Access-Control-Allow-Credentials
为true
表示服务端允许请求携带cookie。如果需要携带cookie的话,浏览器端也要进行设置,设置请求的withCredentials
属性为true
,这样才能成功在请求中传输cookie。Access-Control-Expose-Headers
可选,表示指定XHR请求可通过getResponseHeader()获取到的首部响应字段。
简单跨域请求举例(忽略部分字段):
// 请求头
GET /api HTTP/1.1
Origin:http://localhost.a.com:8080
content-type:'text/plain'
复制代码
// 响应头
HTTP/1.1 200 OK
Access-Control-Allow-Origin:http://localhost.a.com:8080
复制代码
带cookie的简单请求
客户端的准备:
开发者必须在AJAX请求中打开withCredentials属性:表示客户端会携带cookie。
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
服务端的准备:
Access-Control-Allow-Credentials:true
// 请求头
GET /api HTTP/1.1
Origin:http://localhost.a.com:8080
content-type:'text/plain'
复制代码
// 响应头
HTTP/1.1 200 OK
Access-Control-Allow-Origin:http://localhost.a.com:8080
Access-Control-Allow-Credentials:true //服务器允许携带cookie
复制代码
关于cookie的传输:
如果该请求需要携带cookie,Access-Control-Allow-Origin
字段就不能设置为*,有利于保护用户的隐私。并且在携带cookie的时候,只有服务器域名下的cookie才能被携带(详细参考第一方cookie和第三方cookie)。
非简单请求
也就是说浏览器会发送两次http请求。第一次Request Method: OPTIONS,第二次再请求所需内容。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为预检请求
(preflight)。
- 预检请求
// 请求头
OPTIONS /api HTTP/1.1
Origin:http://localhost.a.com:8080
Access-Control-Request-Method:POST
Access-Control-Request-Headers:content-type
复制代码
预检请求时,浏览器会先将此次跨域请求的方法(POST)、额外的请求头部(content-type不是那三种,或者其他的不在简单请求限制中的头部)发送给服务端,服务端查看Origin、Access-Control-Request-Method、Access-Control-Request-Headers
字段,来检查是否允许此次请求。
如果服务器不同意此次请求的话,就返回一个正常的http请求,没有任何与CORS相关的字段,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获,为跨域错误。
如果同意此次请求的话,就返回以下与CORS相关的字段:
// 响应头
HTTP/1.1 200 OK
Access-Control-Allow-Origin:http://localhost.a.com:8080
Access-Control-Allow-Headers:content-type
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Max-Age:1728000
复制代码
Access-Control-Allow-Headers
表示服务器支持的所有头信息字段,Access-Control-Allow-Methods
表示服务器支持的所有请求方法。Access-Control-Max-Age
表示预检请求过期的时间。时间之内不用再次进行预检请求。
接下来,就会发正式请求。
返回所有支持的头部信息和方法是为了避免进行多次预检请求。
- 正式请求
// 请求头
POST /api HTTP/1.1
Origin:http://localhost.a.com:8080
content-type:application/json
复制代码
// 响应头
HTTP/1.1 200 OK
Access-Control-Allow-Origin:http://localhost.a.com:8080
复制代码
简单请求和非简单请求的正式请求是一样的。
带cookie的非简单请求
- 预检请求
// 请求头
OPTIONS /api HTTP/1.1
Origin:http://localhost.a.com:8080
Access-Control-Request-Method:POST
Access-Control-Request-Headers:content-type
复制代码
预检请求不会携带cookie。
// 响应头
HTTP/1.1 200 OK
Access-Control-Allow-Origin:http://localhost.a.com:8080
Access-Control-Allow-Headers:content-type
Access-Control-Allow-Credentials:true //仍需该字段
复制代码
- 正式请求
// 请求头
POST /api HTTP/1.1
Origin:http://localhost.a.com:8080
content-type:application/json
cookie:a=1,b=...
复制代码
开发者必须在AJAX请求中打开withCredentials属性:表示客户端会携带cookie。
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
// 响应头
HTTP/1.1 200 OK
Access-Control-Allow-Origin:http://localhost.a.com:8080
Access-Control-Allow-Headers:content-type
Access-Control-Allow-Credentials:true //服务器允许携带cookie
复制代码
参考文章: