开发的时候遇到JS跨域问题,下面将整理的跨域问题解决方案整理下来,当其他小伙伴遇到此问题时,参考一下。
一 跨域基础知识
1. 同源政策
1) 什么是同源政策
浏览器安全的基石是"同源政策"。同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。
2) 限制范围
随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,共有三种行为受到限制。
- Cookie、LocalStorage 和 IndexDB 无法读取。
- DOM 无法获得。
- AJAX 请求不能发送。
3) 参考文档
同源策略详细内容见:
http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
2. CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求。
实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信。
二 Ajax请求
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。两种请求的处理方式有所不同。
1. 简单请求
同时满足以下两大条件,就属于简单请求。
1) 请求方法是以下三种之一
- HEAD
- GET
- POST
2) HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
2. 简单请求流程
1) 发送Request
对于简单请求,浏览器在头信息之中,会增加一些信息,其中需要特别注意的是一个Origin字段。如下所示:
Accept:application/json, text/javascript, */*; q=0.01
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7,zh-TW;q=0.6
Cache-Control:no-cache
Connection:keep-alive
Content-Length:140
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
Host:jssdk.test.wuzhengfei.cn:7080
Origin:http://jssdk.test.wuzhengfei.cn
Pragma:no-cache
Referer:http://jssdk.test.wuzhengfei.cn/html/jssdk.html
User-Agent:Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1
Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口),服务器根据这个值,决定是否同意这次请求。
2) 收到Response
浏览器收到一个正常的HTTP相应后,发现是跨域请求的响应时,会检查Response Header中是否包含Access-Control-Allow-Origin字段,如果没有或者和origin不匹配,浏览器就认为这是非法的源,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,此时HTTP回应的状态码有可能是200。
Access-Control-Allow-Credentials:false
Access-Control-Allow-Headers:Content-Type
Access-Control-Allow-Methods:*
Access-Control-Allow-Origin:*
Access-Control-Max-Age:100
Content-Type:application/json;charset=UTF-8
Date:Tue, 12 Dec 2017 07:05:04 GMT
Server:Apache-Coyote/1.1
Transfer-Encoding:chunked
3) 跨域相关的header
a) Access-Control-Allow-Origin
此字段是必须的。
服务端告诉浏览器接受那些源,它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
b) Access-Control-Allow-Credentials
该字段可选。
它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
另外,如果需要发送Cookie到服务端,需要在AJAX请求中打开withCredentials属性。否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。处理方式如下:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
注意:
- 省略withCredentials设置时,有的浏览器还是会一起发送Cookie。这时,此时可以显式关闭withCredentials。
- 如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。
c) Access-Control-Expose-Headers
该字段可选。
CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
3. 非简单请求
不满足简单请求两个条件的都属于此范畴。
1) 两次请求
非简单请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight),即OPTIONS请求。
浏览器先发起OPTIONS请求询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用那些HTTP头字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。OPTIONS正常时,继续发送正式请求。
2) 示例说明
a) Ajax请求
function doPost(params, hearders, successCallback) {
var timestamp = jsSignResult.timestamp;
var params = {};
var sign = doSign(params, hearders);
hearders.sign = sign;
$.ajax({
url : config.frog_url,
data : {},
type : "POST",
dataType : 'json',
beforeSend : function(xhr) {
$.each(hearders, function(key, value) {
xhr.setRequestHeader(key, value);
});
},
success : function(result, status, xhr) {
successCallback(result);
}
});
}
b) 原理分析
以上Ajax请求中,我们在header中加入了一些header,不满足简单请求的条件,所以此请求会分为两次:OPTIONS请求+POST请求。
à OPTIONS
请求时,需要服务端在response的Header中加入以下内容。以下代码是允许浏览器提交任何源的数据,允许的请求方法是:
//允许浏览器接收任何源的数据。
response.addHeader("Access-Control-Allow-Origin", "*");
//允许POST,GET,OPTIONS,DELETE,PUT方法
response.addHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT");
response.addHeader("Access-Control-Max-Age", accessControlMaxAge + "");
List<String> headerNames = SystemParametersEnum.getNames();
String headers = CommonUtil.list2String(headerNames);
//添加允许的header,用于后面POST请求时设置自定义header到服务端。
response.addHeader("Access-Control-Allow-Headers", headers);
à POST请求
beforeSend方法中设置的header必须在Access-Control-Allow-Headers之内,否则出错。
3) 跨域相关的header
a) Access-Control-Allow-Methods
该字段必须的。
用来列出浏览器的CORS请求允许那些HTTP方法。
b) Access-Control-Allow-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。
c) Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。示例中为100秒。在此值的有效期内,不用发出另一条预检请求。
d) Access-Control-Allow-Origin
见简单请求中的介绍。
4) 请求头
上面示例的response header、request header如下。
à Response Header示例
Access-Control-Allow-Headers:method,format,appkey,appsecret,accessToken,v,timestamp,source,reqIp,sign,serviceName,jssign,pageurl,noncestr
Access-Control-Allow-Methods:POST,GET,OPTIONS,DELETE,PUT
Access-Control-Allow-Origin:*
Access-Control-Max-Age:100
code:0
Content-Length:5179
Content-Type:application/json;charset=UTF-8
Date:Tue, 12 Dec 2017 07:33:58 GMT
Server:Apache-Coyote/1.1
à Request Header示例
Accept:application/json, text/javascript, */*; q=0.01
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7,zh-TW;q=0.6
appkey:94603712
Connection:keep-alive
Content-Length:0
Host:test.wzf.com:8080
jssign:460770f84543db91454d68b9bfcef45fc4fece02
method:wuzhengfei.method1
noncestr:629916ee-d5f0-4115-9f8d-06b903dd0f91
Origin:http://jssdk.test.
wuzhengfei.cn
pageurl:http%3A%2F%2Fjssdk.test.wuzhengfei.cn%2Fhtml%2Fjssdk.html%3Fa%3D12%26b%3D%E4%B8%AD%E6%96%87%26c%3D100%25
Referer:http://jssdk.test.wuzhengfei.cn/html/jssdk.html
sign:7BBA2E57957095D97E14D0F9579E44E7
timestamp:1513064038835
User-Agent:Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1
三 ajax跨域的表现
ajax请求时,如果存在跨域现象,并且没有进行解决,会有如下表现:
1. 现象一
1) 错误信息
No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 404
2 原因
本次ajax请求是“非简单请求”,所以请求前会发送一次预检请求(OPTIONS)。
服务器端后台接口没有允许OPTIONS请求,导致无法找到对应接口地址。
2. 现象二
1) 错误信息
No 'Access-Control-Allow-Origin' header is present on the requested resource和The response had HTTP status code 405
2) 原因
后台方法允许OPTIONS请求,但是一些配置文件中(如安全配置),阻止了OPTIONS请求,才会导致这个现象
3. 现象三
1) 错误信息
No 'Access-Control-Allow-Origin' header is present on the requested resource
2) 原因
服务端正常处理并返回数据给浏览器了,只是浏览器在进行请求头校验的时候,发现当前的来源和response中允许的来源(Access-Control-Allow-Origin)不匹配,所以触发了XHR.onerror方法,前端报错。
四 跨域解决方案
1. CORS
在上面讲述Ajax简单请求和非简单请求时已经讲过讲过解决办法了,这里整理一下处理思路。
1) 简单请求
服务端通过Origin决定是否接受客户端请求。
服务端返回数据时通过在Response Header中添加Access-Control-Allow-Origin字段,允许浏览器显示跨源的数据。
2) 非简单请求
非简单请求的核心是在OPTIONS请求时在Response Header中添加内容(Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Max-Age、Access-Control-Allow-Headers),以做到控制正式请求提交数据的目的。
服务端收到OPTIONS时,可以根据Origin的内容决定是否接受请求;
AJAX正式请求时,提交的数据必须满足OPTIONS响应头中规范。
2. JSONP
1) 原理
JSONP(JSON with Padding)之所以能够用来解决跨域方案,主要是因为 <script> 脚本拥有跨域能力,而JSONP正是利用这一点来实现的。用JSONP获取到的信息时javascript,而不是JSON,所以需要使用JavaScript直译器执行,而不是用JSON解析器解析。
对于这一点也比较好理解,在html中,我们可以引用其他域名下的资源,最常见的就是js、css、img这些静态资源,如下示例:
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.js"></script>
<script src="../js/jssdk.js"></script>
2) 实现步骤
a) 在html中添加<script>元素
客户端通过添加一个<script>元素到html页面,向服务器请求JSON数据,这种做法不受同源政策限制。
b) 服务端返回callback
String callback = apiService.getRequest().getParameter("callback");
return callback + "(" + JSON.toJSONString(response)+ ")";
服务端返回的不是json,而是callback方法。
c) 客户端执行callback方法
浏览器收到服务端返回内容后,直接使用javascript直译器执行。
d) 源码
如果对实现原理感兴趣,可以参考一下项目
https://github.com/jaubourg/jquery-jsonp
3) 使用注意
基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求。
3. 对比
CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
五 参考文档
跨域资源共享 CORS 详解
http://www.ruanyifeng.com/blog/2016/04/cors.html
浏览器同源政策及其规避方法
http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
ajax跨域原理以及解决方案
https://www.cnblogs.com/bojuetech/p/5895767.html