Ajax跨域解决方案

简介: 讲述跨域原因以及从原理上讲述两种解决方案:jsonp,options

开发的时候遇到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

相关实践学习
基于函数计算快速搭建Hexo博客系统
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
相关文章
|
8月前
|
JSON 前端开发 JavaScript
AJAX(GET POST请求、 jQuery axios 发送请求、跨域--cors、请求超时、网络异常、放弃请求、重复发送请求)(三)
AJAX(GET POST请求、 jQuery axios 发送请求、跨域--cors、请求超时、网络异常、放弃请求、重复发送请求)(三)
|
3月前
|
JSON 前端开发 安全
浏览器跨域限制:为什么浏览器不能跨域发送Ajax请求?
浏览器跨域限制:为什么浏览器不能跨域发送Ajax请求?
37 0
|
9月前
|
前端开发 JavaScript
Echarts实战案例代码(22):jquery使用ajax属性beforeSend实现预加载loading效果代替showLoading的解决方案
Echarts实战案例代码(22):jquery使用ajax属性beforeSend实现预加载loading效果代替showLoading的解决方案
73 0
|
7月前
|
缓存 JSON 前端开发
Ajax:跨域与JSONP
Ajax:跨域与JSONP
41 1
|
8月前
|
缓存 JSON 前端开发
AJAX(GET POST请求、 jQuery axios 发送请求、跨域--cors、请求超时、网络异常、放弃请求、重复发送请求)(二)
AJAX(GET POST请求、 jQuery axios 发送请求、跨域--cors、请求超时、网络异常、放弃请求、重复发送请求)(二)
|
8月前
|
XML 数据采集 Web App开发
AJAX(GET POST请求、 jQuery axios 发送请求、跨域--cors、请求超时、网络异常、放弃请求、重复发送请求)(一)
AJAX(GET POST请求、 jQuery axios 发送请求、跨域--cors、请求超时、网络异常、放弃请求、重复发送请求)
|
9月前
|
JSON 前端开发 安全
ajax中实现访问url已阅即焚的解决方案(url动态参数、变量加密、常量不变、php加密解密、API访问验证方式)
ajax中实现访问url已阅即焚的解决方案(url动态参数、变量加密、常量不变、php加密解密、API访问验证方式)
180 0
|
9月前
|
JSON 前端开发 JavaScript
layui上传文件弹出请求上传接口出现异常的终极解决方案(v2.68版本、ajax底层逻辑修改、debug快速定位)
layui上传文件弹出请求上传接口出现异常的终极解决方案(v2.68版本、ajax底层逻辑修改、debug快速定位)
481 0
|
9月前
|
前端开发 API PHP
ajax获取API数据出现400或415的解决方案
ajax获取API数据出现400或415的解决方案
81 0
|
9月前
|
前端开发 API PHP
漏刻有时开放平台数据接口php允许ajax跨域的解决方案
漏刻有时开放平台数据接口php允许ajax跨域的解决方案
35 0