什么是跨域
跨域是浏览器受同源(协议、域名、端口)策略的限制,不允许不同源的站点之间进行某些操作(如发送ajax请求,操作dom,读取cookie),如果不进行特殊配置是不能操作成功的,并且控制台会报如下跨域错误:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'xxxxxx' is therefore not allowed access
跨域出现的场景
两个常见的例子:
-
前后端分离的项目联调时,客户端和服务端ip不一致
一般前端本地服务启动在localhost:8080上,服务端接口部署在联调服务器上,此时向联调服务器发送请求的话就会发生跨域
-
大型项目中可能需要多个服务,不同职责的服务部署在不同的端口上,甚至多个服务器上
在当前网站页面上请求其他服务器或者其他端口上的接口,也会发生跨域,这种情况一般通过nginx反向代理解决
跨域解决方案
JSONP
原理:利用script标签的src属性不受同源策略的限制,并且资源加载完成后会被当作js脚本立即执行的特点,来达到跨域请求资源的目的。
需要做一些特殊处理:准备一个callback函数用于处理后端传来的数据,将callback函数的名字作为src属性中的query传给后端,后端收到后用callback函数名将数据包裹起来,使数据作为参数返回给前端,当资源加载完成,callback会立即被调用,此时的实参就是我们需要的数据。
javascript
var script = document.createElement('script');
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
服务端返回如下(返回时即执行全局函数):
json
handleCallback({"success": true, "user": "admin"})
JSONP的优缺点:
- 优点
- 兼容性好,支持老的浏览器
- 缺点
- 只支持get请求,因为script标签请求资源本质就是一个get请求
- 需要服务端专门配置,把数据用callback函数名包裹起来再返回
CORS
CORS是w3c指定的跨域方案,支持所有类型的请求;兼容性:ie不能低于ie 10
CORS跨域方案将所有请求划分为简单请求和非简单请求两类,对其分别采用不同的处理方案。
简单请求
同时满足以下两个条件的请求属于简单请求:
- 请求方法是
get
、post
、head
中的一种 - http头字段不超出:
Accept
Accept-Language
Accept-Language
Content-Type
仅限于text/plain
、multipart/form-data
、application/x-www-form-urlencoded
简单请求流程
浏览器在请求头中自动加入origin字段,origin字段用来说明本次请求来自哪个源(协议+域名+端口),服务器根据这个值,决定是否同意这次请求。
如果origin指定的源,不在许可范围内,服务器会返回一个正常的http回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest
的onerror
回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
如果Origin指定的域名在许可范围内,服务器返回的头字段中会包含Access-Control-Allow-Origin,它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
如何设置cookie?
- cors请求默认不携带cookie,如果想要发送cookie,需要服务端和客户端同时设置
- 一方面要服务器同意,指定
Access-Control-Allow-Credentials: true
- 另一方面,开发者必须在ajax请求中打开
withCredentials
属性。xhr.withCredentials = true;
非简单请求
预检请求
非简单请求需要先发出一个预检请求,预检请求方法是OPTIONS,用来获知服务器是否允许该实际请求。"预检请求"的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响(PUT、delete)
请求头字段
Origin
Access-Control-Request-Method
Access-Control-Request-Headers
预检请求的回应
- 如果允许跨源请求
Access-Control-Allow-Origin
Access-Control-Allow-Methods
(返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。)Access-Control-Allow-Headers
(是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。)Access-Control-Allow-Credentials
- 如何设置cookie?
- cors请求默认不发送cookie,如果想要发送cookie,需要服务端和客户端同时设置
- 一方面要服务器同意,指定
Access-Control-Allow-Credentials
: true - 另一方面,开发者必须在AJAX请求中打开
withCredentials
属性。
- 如何设置cookie?
Access-Control-Max-Age
- 该字段可选,用来指定本次预检请求的有效期,单位为秒。在此期间,不用发出另一条预检请求。
- 如果不允许,比如origin不在信任名单内
- 会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。浏览器就会报错
正常请求
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段。
反向代理
跨域是浏览器的保护机制,如果绕过浏览器,使用代理服务器去请求目标服务器上的数据,就不会受跨域影响。因此前端可以通过脚手架或webpack配置devSever下的proxy选项,将/api开头的请求转发到真实服务器上。
在生产环境下也可以使用nginx配置反向代理来解决跨域。