接口开发-跨域访问-解决方案简单汇总
1 引言
概念
首先什么是跨域,简单地理解就是因为JavaScript同源策略的限制,a.com 域名下的js无法操作b.com或是c.a.com域名下的对象,也就是不同域名之间相互访问。比如我们在本地访问一个其他服务器上的接口时往往出现下面的情况:
这就说明出现了跨域问题。下面我先说明几个概念
同源策略
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
- 所谓同是指:
* 域名相同
* 协议相同
* 端口号相同
URL | 说明 | 是否允许通信 |
---|---|---|
http://www.a.com/a.js http://www.a.com/b.js | 同一域名下 | 允许 |
http://www.a.com/lab/a.js http://www.a.com/script/b.js | 同一域名下不同文件夹 | 允许 |
http://www.a.com:8000/a.js http://www.a.com/b.js | 同一域名,不同端口 | 不允许 |
http://www.a.com/a.js https://www.a.com/b.js | 同一域名,不同协议 | 不允许 |
http://www.a.com/a.js https://123.456.78.9/b.js | 域名不同 | 不允许 |
http://www.a.com/a.js https://script.a.com/b.js | 主域相同,子域不同 | 不允许 |
http://www.a.com/a.js https://a.com/b.js | 同一域名,不同二级域名 | 不允许 |
跨域问题就是违背了同源策略
ajax
AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。传统网页在更新网页内容时哪怕是很小一部分都需要重载整个页面。但是使用ajax之后,通过在后台与服务器进行少量数据交换,实现异步更新,也就是可以在不重新加载整个网页的情况下,对网页的某部分进行更新。简单粗暴的说就是我们利用ajax从从后台访问数据,这就牵扯到了一个问题,跨域问题!
跨域
跨域的概念刚才简单介绍了,举个例子吧:
如果在A网站中,我们希望使用Ajax来获得B网站中的特定内容,如果A网站与B网站不在同一个域中,那么就出现了跨域访问问题,你可以理解为两个域名之间不能跨过域名来发送请求或者请求数据,否则就是不安全的。但是解决的方式呢:
- 解决跨域问题的方法
* jsonp
* iframe跨域
* html5中的postMessage()方法
* 服务器代理
* 设置cors
1.1 编写目的
编写接口开发跨域访问解决方案汇总的主要目的是,归纳整理目前可供选用的解决接口跨域访问的解决方案。
接口调用开发模式需要考虑一个重要的问题,即跨域访问问题。
跨域访问有两个方向的解决策略:
- 将前端页面和接口部署在统一域名下,则不存在跨域访问的问题;
- 前端页面和接口部署在不同的域名下,通过技术解决跨域访问的技术难点。本文主要描述的是前端页面和接口部署在不同的域名下,可用选择的跨域解决方案。
1.2 概念简介
同源策略:阻止从一个源加载的文档或脚本获取或设置另一个源上加载的文档的属性。这个策略可以追溯至Netscape Navigator 2.0。
简单地说就是要求动态内容(例如,JavaScript或者VBScript)只能阅读与之同源的那些HTTP应答和Cookies,而不能阅读来自不同源的内容。为了形象地进行展示,下表罗列了几类URL的同源检测结果。
序号 | URL | 结果 | 说明 |
---|---|---|---|
1 | http://www.demo.cn/demo/other.html | - | 原地址 |
2 | http://www.demo.cn/demo/other2.html | 成功 | 同一域名同一文件夹下允许 |
3 | http://www.demo.cn/demo2/other.html | 成功 | 同一域名不同文件夹下允许 |
4 | https://www.demo.cn/demo/other.html | 失败 | 协议不同不允许 |
5 | http://www.demo.cn:8080/demo/ot.html | 失败 | 端口号不同不允许 |
6 | http://192.168.7.38/demo/other.html | 失败 | 域名对应ip和域名之间不允许 |
7 | http://script.demo.cn/demo/oer.html | 失败 | 主域名相同,子域名不同,不允许 |
8 | http://demo.cn/demo/other.html | 失败 | 同一域名,不同二级域名,不允许 |
9 | http://www.api.cn/demo/other.html | 失败 | 不同域名之间不允许 |
由上表总结:在协议、域名相同时,不涉及跨域访问;其他情况下,都涉及跨域访问。
2 解决方案
解决跨域访问可用的解决方案有多种,本为整理的有五种:JSONP、CORS、FLASH、PROXY、IFRAME。下面分别从原理、优点、缺点、开发复杂度和示例五个方面对五种解决方案进行描述。
2.1 JSONP
2.1.1 原理
在同源策略下,在某个服务器下的页面是无法获取到该服务器以外的数据的,但img、iframe、script等标签是个例外,这些标签可以通过src属性请求到其他服务器上的数据。
JSONP就是通过script节点src调用跨域的请求。当我们通过JSONP模式请求跨域资源时,服务器返回给客户端一段javascript代码,这段javascript代码自动调用客户端回调函数。
2.1.2 优点
使用方便,同时支持大多部分浏览器版本。
2.1.3 缺点
只支持GET提交方式,不支持其他POST提交。
2.1.4 开发复杂度
与同源开发相比,只需前端页面和后台接口修改少量代码即可。
2.1.5 示例
前台页面:
$.ajax({
url:'http://192.168.7.38:8080/telapi/LoginAction.do?method=login',
dataType:"jsonp",
data:{username:'zhangsan',password:'0'},
jsonpCallback:"success_jsonp",
success:function(data){
$("#te").val(data);
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
后台接口:
response.setContentType("text/html;charset=utf-8");
PrintWriter pw = response.getWriter();
pw.write("success_jsonp(" + result + ")");
pw.flush();
pw.close();
- 1
- 2
- 3
- 4
- 5
2.2 CORS
2.2.1 原理
Cross-Origin Resource Sharing,W3C制定的跨域资源分享标准。post前会产生一次options嗅探(称之为preflight,但简单请求不会出现)来确认有否跨域请求的权限;客户端post时会带上Origin头指示来源网站,服务端响应时需带上Access-Control-Allow-Origin头与Origin头的值匹配,以示许可。
2.2.2 优点
W3C标准方案,实现简单。
2.2.3 缺点
支持的浏览器有限,IE需要较高版本,具体浏览器支持情况见下图。
2.2.4 开发复杂度
与同源开发相比,只需后台接口增加少量代码。
2.2.5 示例
前台页面:
//前端页面使用普通的Ajax提交方式,跟同源访问一样,无需更改。
- 1
后台接口:
//这句代码中*代码,服务器允许任何人访问。当然可以设置规定访问的域名。
//比如只允许http://localhost:8080/crcp这个域下的访问。则把*代替成这个域名即可。
response.setHeader("Access-Control-Allow-Origin", "*");
- 1
- 2
- 3
2.3 FLASH
2.3.1 原理
利用的swf格式文件跨域post提交数据,需要部署crossdomain.xml。JavaScript 将数据提交给本域的 Flash,通过 Flash 中转去访问其他域的接口,条件只需要其他域的根目录下有一个crossdomain.xml文件,文件中设置允许所有域名或允许本域访问即可。
2.3.2 优点
ADOBE标准方案,相对CORS兼容性佳,相对invisible iframe响应数据量较大的时候优势明显。
2.3.3 缺点
依赖flash(要求flash9及以上)。
2.3.4 开发复杂度
与同源开发相比,前端页面需引入swf,并需要在form中用自定义post方法调用接口。后台接口需做少量代码修改。
2.3.5 示例
此示例参考“张宴”的文章:http://blog.s135.com/ajaxcdr/。
在“Cross-domain AJAX using Flash”的基础上,增加了对表单进行智能处理的功能,封装了一个JavaScript包:AJAXCDR。通过 AJAXCDR,即可轻松地解决 JavaScript 和 AJAX 跨域 HTTP POST/GET 表单请求,支持IE、Firefox、谷歌Chrome等多种浏览器。
AJAXCDR 拥有两个文件:ajaxcdr.js 和 ajaxcdr.swf,AJAXCDR 拥有一个 JavaScript 函数 AjaxCrossDomainRequest() 和一个全局变量 AjaxCrossDomainResponse。
AJAXCDR 函数说明:
1、JavaScript函数:
AjaxCrossDomainRequest(URL, Method, FormName, CallBack):
参数说明:
URL:需要访问的URL地址,相当于表单的action=的值。
Method:方法,本函数支持POST和GET方法,相当于表单的method=的值。
FormName:表单名称,相当于表单的name=的值。
CallBack:回调函数,请求完成后,回调用户的一个函数,用户可以在该函数内对返回值进行处理。
#
AjaxCrossDomainResponse:
当用户调用AjaxCrossDomainRequest()函数完成 HTTP POST/GET 请求后,该函数会把服务器端返回的数据写入到AjaxCrossDomainResponse变量中,您可以通过AjaxCrossDomainResponse变量获取返回值。
前台页面:
<form name="cross_domain_demo">
<input name="title" type="text" value="测试数据">
</form>
<a href="javascript:AjaxCrossDomainRequest('http://api.bz/ajaxcdr/echo.php', 'POST', 'cross_domain_demo', 'mycallback()');">提交</a>
<script type="text/javascript"> function mycallback(){ alert(AjaxCrossDomainResponse); } </script>
<script type="text/javascript" src="/demo/ajaxcdr/ajaxcdr.js"></script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
后台接口:
<?php
header("Cache-Control: no-cache, must-revalidate");
var_export($_REQUEST);
?>
- 1
- 2
- 3
- 4
2.4 PROXY
2.4.1 原理
当前域实现一个代理,所有向外部域名发送的请求都经由该代理中转。
举例说明(以asp.net为例):页面a.aspx在域domain1.com中,页面b.aspx在域domain2.com中,a.aspx通过ajax请求b.aspx数据则为跨域。在域domain1.com放置代理页面temp.aspx,那么a.aspx访问temp.aspx就是同一域了。而temp.aspx再去访问b.aspx返回数据给a.aspx,这样问题是不是已经解决了呢,temp.aspx访问b.aspx不也是跨域访问吗?这就是重点:a.aspx访问temp.aspx发送请求时已通过Form身份验证了,请求已到达服务器端,而temp.aspx在服务器端访问获取b.aspx的数据则不存在Form身份验证,所以代理页面temp.aspx代码应该运行在服务器端,也就是将获取数据的代码写到temp.cs当中即可。
2.4.2 优点
对同源开发的代码修改较少。
2.4.3 缺点
需配置代理,数据中转低效。
2.4.4 开发复杂度
与同源开发相比,前端页面需少量代码修改,后台接口需添加并配置代理。
2.4.5 示例
用Nginx反向代理实现跨域,只需要修改Nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。
我们只需要配置Nginx,在一个服务器上配置多个前缀来转发http/https请求到多个真实的服务器即可。这样,这个服务器上所有url都是相同的域名、协议和端口。因此,对于浏览器来说,这些url都是同源的,没有跨域限制。而实际上,这些url实际上由物理服务器提供服务。这些服务器内的javascript可以跨域调用所有这些服务器上的url。
下面,给出一个Nginx支持跨域的例子,进行具体说明。
如,我们有两个pythonflask开发的项目:testFlask1和testFlask2。
testFlask2项目上的javascript脚本要通过ajax方式调用testFlask1的一个url,获取一些数据。
正常情况下部署,就会有跨域问题,浏览器拒绝执行如下这样的调用。
$("button").click(function () {
$.get("127.0.0.1:8081/partners/json", function (result) {
$("div").html(result);
});
- 1
- 2
- 3
- 4
- 5
下面把testFlask2项目的javascrip文件修改一下。这样访问同源的url,就不会有跨域问题。
$("button").click(function () {
$.get("partners/json", function (result) {
$("div").html(result);
});
- 1
- 2
- 3
- 4
- 5
但是,我们testFlask2项目实际上没有partners/json这样的url,那怎么处理呢?
我们这样编写Nginx的配置文件:
server{
listen8000;
location/ {
includeuwsgi_params;
uwsgi_passunix:/tmp/testFlask2.sock;
}
location/partners {
rewrite^.+partners/?(.*)$ /$1 break;
includeuwsgi_params;
uwsgi_passunix:/tmp/testFlask1.sock;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
我们把testFlask2项目部署在8080端口的根目录下。把提供web服务的testFlask1项目部署在/partners目录下。但我们的testFlask1项目并不能处理/partners/json这样的url请求。那怎么办呢?通过rewrite^.+partners/?(.*)$ /$1 break;
这一条命令,Nginx可以把收到的/partners/*
请求全部转为/*请求后再转发给背后的真实web服务器。这样,RESTFUL的ajax客户端程序,只需要给出特定前缀的url就可以调用任意服务器提供的RESTFUL接口了。
甚至,通过Nginx的反向代理,我们还能调用其他公司开发的网站提供的RESTFUL接口。如:
location/sohu {
rewrite^.+sohu/?(.*)$ /$1 break;
includeuwsgi_params;
proxy_passhttp://www.sohu.com/;
}
- 1
- 2
- 3
- 4
- 5
- 6
我们就把sohu网站整个搬到我们的8080:/sohu/目录下了,我们的javascript就可以尽情调用其RESTFUL服务了。顺便说一下,rewrite^.+sohu/?(.*)$ /$1 break;
这句命令中,$1
表示(.*)
这个部分。第一对()内的参数是$1
,第二对()内的参数就是$2
,以此类推。
Nginx是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。Nginx作为反向代理服务器,就是把http请求转发到另一个或者一些服务器上。
通过把本地一个url前缀映射到要跨域访问的web服务器上,就可以实现跨域访问。
对于浏览器来说,访问的就是同源服务器上的一个url。而Nginx通过检测url前缀,把http请求转发到后面真实的物理服务器。并通过rewrite命令把前缀再去掉。这样真实的服务器就可以正确处理请求,并且并不知道这个请求是来自代理服务器的。
简单说,Nginx服务器欺骗了浏览器,让它认为这是同源调用,从而解决了浏览器的跨域问题。又通过重写url,欺骗了真实的服务器,让它以为这个http请求是直接来自与用户浏览器的。
这样,为了解决跨域问题,只需要动一下Nginx配置文件即可。
2.5 IFRAME
2.5.1 原理
通过js动态生成不可见表单和iframe,将表单的target设为iframe的name以此通过iframe做post提交。提交后由于跨域,无法直接读取响应内容。一般的做法是,iframe内通过js改变自身location的fragment,外部则监听iframe的onload事件,读取fragment的内容。有现成的跨域iframe通信类库,如jQuery PostMessage Plugin。
2.5.2 优点
兼容性佳,facebook,google,新浪已/曾采用
2.5.3 缺点
依赖hack实现,响应数据量大时需要切片、多次设置fragment并轮询,响应频繁时可能失效。据说Firefox等可能不支持读取另一个iFrame的内容。
2.5.4 开发复杂度
2.5.5 示例
1.创建一个iframe
try{
// IE6, IE7
iframe = document.createElement('<iframe name="{iframeName}">');
} catch(e) {
iframe = document.createElement('iframe');
iframe.name = {iframeName};
}
iframe.style.display = 'none';
document.body.appendChild(iframe);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.监听iframe的onload事件
if(iframe.readyState){
iframe.onreadystatechange = function(){
if (iframe.readyState && iframe.readyState=='complete'){
callbackFunction.apply(this);
}
}.bind(this);
} else {
iframe.onload = callbackFunction.bind(this);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3.创建表单,并自动提交
form = document.createElement('form');
form.action = {url};
form.target = {iframeName}; // important!!
form.method = 'post';
input = document.createElement('input');
input.name = {inputName};
input.value = {inputValue};
input.type = 'hidden';
form.appendChild(input);
document.body.appendChild(form);
form.submit();
/** 备注 1. form.target 必须要与iframe.name相同;当表单提交后,页面会target到隐藏的iframe,并且不刷新页面,实现跨域。 2. form表单必须要append到页面上,否则不能使用js提交(chrome除外)。 **/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
4.callbackFunction 获取iframe的内容
iframeContent = iframe.contentDocument? iframe.contentDocument: iframe.contentWindow.document;
- 1
5.php的返回值
<!DOCTYPE html>
<html>
<body>
<script>document.domain="xxxx.com";</script>
<script type="text/json-result">'.json_encode($result).'</script>
</body>
</html>
/** 备注 **/
1. 在IE下,必须要是完整的html页面才能找到document对象
2. 返回的结果,要加入document.domain,确保可以跨域访问
3. 返回的结果放到script标签中,标签可以采用特殊的type标注,以便在js中获取结果
4. js中获得的json是string,可以通过evalJSON()将其转为json数据
/** 备注 **/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
3 总结
通过章节2,可以详细地了解五种解决方案的优劣之处,为了方便对这些解决方案有直观的对比,下表2列出了五种解决方案的优劣对比结果。
序号 | 解决方案 | GET | POST | 版本支持 | 易用性 | 稳定性 | 效率 | 依赖 |
---|---|---|---|---|---|---|---|---|
1 | JSONP | √ | × | 良好 | 简单 | 良好 | 良好 | - |
2 | CORS | √ | √ | 一般 | 简单 | 良好 | 良好 | - |
3 | FLASH | √ | √ | 良好 | 一般 | 良好 | 良好 | FLASH9 |
4 | PROXY | √ | √ | 良好 | 一般 | 良好 | 一般 | 代理支持 |
5 | IFRAME | √ | √ | 良好 | 一般 | 一般 | 良好 | - |
版权声明:本文为博主hanchao5272原创文章,转载请注明来源,并留下原文链接地址,谢谢! https://blog.csdn.net/hanchao5272/article/details/79278514
还没有评论,来说两句吧...