1 前言

 之前项目有遇到过跨域问题,当时有用过iframe, 现在的项目中使用了

SimpleCORSFilter,

还是 CORS解决起来方便一些。请看 2 解决方案 。

 

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);
}
});

后台接口:

response.setContentType("text/html;charset=utf-8");
PrintWriter pw = response.getWriter();
pw.write("success_jsonp(" + result + ")");
pw.flush();
pw.close();

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提交方式,跟同源访问一样,无需更改。

后台接口:

//这句代码中*代码,服务器允许任何人访问。当然可以设置规定访问的域名。
//比如只允许http://localhost:8080/crcp这个域下的访问。则把*代替成这个域名即可。
response.setHeader("Access-Control-Allow-Origin", "*");

使用Filter 项目统一解决:

《1》Filter 类

public class SimpleCORSFilter implements Filter {

	public void doFilter(ServletRequest req, ServletResponse res,
						 FilterChain chain) throws IOException, ServletException {
		HttpServletResponse response = (HttpServletResponse) res;
		response.setHeader("Access-Control-Allow-Origin", "*");
		response.setHeader("Access-Control-Allow-Methods",
				"POST, GET, OPTIONS, DELETE");
		response.setHeader("Access-Control-Max-Age", "3600");
		response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
		chain.doFilter(req, res);
	}

	public void init(FilterConfig filterConfig) {
	}

	public void destroy() {
	}


}

  《2》 在web.xml 中添加:

  <filter>
    <filter-name>SimpleCORSFilter</filter-name>
    <filter-class>com.i7colors.filter.SimpleCORSFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>SimpleCORSFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  


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>

后台接口:

<?php
header("Cache-Control: no-cache, must-revalidate");
var_export($_REQUEST);
?>

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);
});

下面把testFlask2项目的javascrip文件修改一下。这样访问同源的url,就不会有跨域问题。

$("button").click(function () {
$.get("partners/json", function (result) {
$("div").html(result);
});

但是,我们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;
}
}

我们把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/;
}

我们就把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);

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);
}

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除外)。
**/

4.callbackFunction 获取iframe的内容

iframeContent = iframe.contentDocument? iframe.contentDocument: iframe.contentWindow.document;

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数据
/** 备注 **/

3 总结
通过章节2,可以详细地了解五种解决方案的优劣之处,为了方便对这些解决方案有直观的对比,下表2列出了五种解决方案的优劣对比结果。

序号 解决方案 GET POST 版本支持 易用性 稳定性 效率 依赖
1 JSONP √ × 良好 简单 良好 良好 -
2 CORS √ √ 一般 简单 良好 良好 -
3 FLASH √ √ 良好 一般 良好 良好 FLASH9
4 PROXY √ √ 良好 一般 良好 一般 代理支持
5 IFRAME √ √ 良好 一般 一般 良好 -

---------------------
感谢作者分享
原文:https://blog.csdn.net/hanchao5272/article/details/79278514
版权声明:本文为博主原创文章,转载请附上博文链接!

posted on 2019-05-21 15:39  waytods  阅读(246)  评论(0编辑  收藏  举报