跨域访问 (Cross-origin)
最近在做一个 vue 前后分离的项目,利用 axios 处理 XHR 信息时,浏览器端报错:
该错误由于前端vue和后端django所属资源域不同而产生(具体原因此不详述),由此接触到跨域访问,索性做一些了解。
简单定义
浏览器的同源策略会导致跨域,这里同源策略又分为以下两种
- DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
- XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。
只要 ++协议、域名、端口++ 之中任意一个不同,都会被当成不同的域,之间的请求被认为是跨域请求。
为什么禁止跨域
AJAX同源策略主要用来防止CSRF攻击。如果没有AJAX同源策略,相当危险。
- 用户登录了自己的银行页面 http://mybank.com,http://mybank.com向用户的cookie中添加用户标识。
- 用户浏览了恶意页面 http://evil.com。执行了页面中的恶意AJAX请求代码。
- http://evil.com向http://mybank.com发起AJAX HTTP请求,请求会默认把http://mybank.com对应cookie也同时发送过去。
- 银行页面从发送的cookie中提取用户标识,验证用户无误,response中返回请求数据。此时数据就泄露了。
-
而且由于Ajax在后台执行,用户无法感知这一过程。
跨域资源共享 CORS (Cross-origin resource sharing)
但是总有那么一些场景,需要XHR跨域进行访问。CORS由此而生。
基本原理
浏览器将CORS请求分为简单请求(simple request)和非简单请求(not-so-simple request)
满足以下2个条件,即为简单请求:
- 请求方法是 [HEAD, GET, POST] 之一
- HTTP 头信息不超过这几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
基本的,对于简单请求,CORS会在请求头加一个字段;而对于非简单请求,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...
如果服务器拒绝,则会返回一个错误,如本文开头的那种。
如果服务器同意请求,Response的头信息中会多加入几个字段
Access-Control-Allow-Origin: http://api.bob.com # 必须字段。 要么是请求时Origin的值,要么是一个 *
Access-Control-Allow-Credentials: true # 可选字段。表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar # 可选字段。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段::
# Cache-Control、Content-Language、Content-Type、
# Expires、Last-Modified、Pragma
# 如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。
Content-Type: text/html; charset=utf-8
withCredentials 属性
如果要把Cookie发送到服务器,一方面要服务器同意,另一方面,请求必须打开 withCredentials 属性。如
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);
需要注意,如果要发送cookie,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的Origin。
非简单请求跨域
非简单请求会在正式请求之前,增加一次http请求,以保证跨域安全。
预请求
此次请求浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest
请求,否则就报错。
预请求头实例:
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT # 列出CORS请求会用到哪些方法
Access-Control-Request-Headers: X-Custom-Header # 指定CORS会额外发送的header字段
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
预请求方法是OPTION
,关键字段 Origin
用于表明身份。
详查 阮一峰-CORS跨域
预请求回应
服务器检查了 Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,确认允许跨源请求,就可以做出回应。
- 如果服务器同意请求,会在response的header中加入
Access-Control-Allow-Origin
字段,该字段值可以是 * ,表示同意此次跨域 - 如果服务器拒绝请求,会返回一个正常HTTP回应,但是不带
Access-Control-Allow-Origin
字段。此时,浏览器会认为服务器拒绝了跨域请求,并进行错误流程。
References: