lanbos'blog

web前后端跨域联调总结

业务情景

pc端和移动端双网页,需要兼容ie8,前后完全分离,页面与接口完全跨域,有post请求。

跨域解决方案总结

使用了2号方案



1. CORS

cors跨域解决方案是当前前端最为通用的解决方案之一,可以说除了无法支持ie8,ie9,携带cookie略有限制之外没有别的缺点。配置过程网上也有详细的介绍较为简单

  • 服务端配置

响应头添加:

1
2
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Origin: http://www.yourspacename.com/*

若要携带cookie则继续增加

1
2
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:withCredentials

允许带 cookie 时,Access-Control-Allow-Origin 字段值不可以设置为 *

  • 前端配置

正常使用ajax请求即可,若是需要携带cookie需要设置withCredentials: true,jq为添加配置:

1
2
3
xhrFields: {
withCredentials: true,
}

注意这个属性不支持ie10及更早版本ie

2. CORS+XDomainRequest

为了兼容ie8/ie9,使用Cross-Domain AJAX for IE8 and IE9
项目中有明确介绍如何配合jq使用,方案有几点值得注意:

  • 无法设置请求头中的自定义字段,即只能使用简单请求
  • 无法携带 cookie
  • post请求只能contentType必须设置为text/plain,传递参数为一组json格式的字符串
  • 只能是异步请求
  • ie8和ie9无法携带Referer

具体配置如下:

  • 服务端配置

CORS配置相同,但是post请求需要注意contentTypetext/plain时php无法用$POST拿到传参,需要使用超全局变量$GLOBALS拿到参数

  • 前端配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// GET
$.getJSON('http://jsonmoon.jsapp.us/').done(function(data) {
console.log(data.name.first);
});

// POST
$.ajax({
url: 'http://frozen-woodland-5503.herokuapp.com/cors.json',
data: 'this is data being posted to the server',
contentType: 'text/plain',
type: 'POST',
dataType: 'json'
}).done(function(data) {
console.log(data.name.last);
});

3. jsonp

如果跨域只有get请求的话使用jsonp是最好的解决方案。

  • 服务端配置
1
2
$callback = isset( $_GET[ 'callback' ] ) ? $_GET[ 'callback' ] : 'callback';
echo $callback . '(' . json_encode( $data ) . ')';

既使用callback参数包装执行。

  • 前端设置

在jquery中设置dataTypejsonp即可,其他与get请求设置基本相同,可设置指定的callback名字配合缓存。

1
2
3
4
5
6
7
8
$.ajax({
url: "http://ajax.yoursite.com/api.php",
dataType: "jsonp",
jsonpCallback:"jsonpName",
success: function( json ){
alert( json );
}
});

4. document.domain+iframe&&window.name+iframe&&postMessage+iframe

这类使用iframe传递信息的跨域方式解决方式大同小异,

  1. 都是在应用页面中嵌入(可以动态生成)一个指向空页面(about:blank)的iframe。
  2. 操作iframe,动态把post数据在iframe中生成一个表单,提交。
  3. 把表单的返回数据传递回应用页面。(若设置document.domain父子域,则可以直接获取iframe内容,若完全跨域使用window.name和postMessage传递)
  4. 销毁iframe
  • 服务端配置

以window.name比较通用的方式为例,服务端返回的数据需要特殊处理,不再返回json,需要返回类似格式

1
<script>window.name='{需要的json字符串}'<\/script>
  • 前端配置

前端处理比较多,上面已经讲了大概逻辑,还要涉及到处理兼容的问题。网上找到了一个封装好的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function postJSONP(url, data, fn) {  
var _doc = document,form = _doc.createElement("form"),
iframeState,key,input,iframe,iframeName = "ifr" + Math.random().toString(16).slice(-6);
//创建表单数据
if (!!data) {
for(key in data) {
input = _doc.createElement("input");
input.type = "hidden";
input.name = key;
input.value = data[key];
form.appendChild(input);
}
}
form.action = url;
form.target = iframeName;
form.method = "post";
_doc.body.appendChild(form);
try {
iframe = _doc.createElement('<iframe name="'+iframeName+'">');//兼容IE6、7
} catch (e) {
iframe = _doc.createElement('iframe');
iframe.name = iframeName;
}
iframe.style.display = "none";
iframe.attachEvent ? iframe.attachEvent("onload", fulfil) :(iframe.onload = fulfil);//事件处理
_doc.body.appendChild(iframe);
form.submit();//表单提交
iframeState = 0;//框架状态记录
function fulfil(){
if(iframeState === 0){
iframeState = 1;
iframe.removeAttribute('name');//解决IE10+获取不到window.name的问题
iframe.contentWindow.location.replace("about:blank");
}else if(iframeState === 1){
iframeState = null;
var json,arr,str = iframe.contentWindow.name;
console.info(str);
try{
json = window.JSON ? JSON.parse(str) : new Function("return " + str)();
}catch(e){
json = {error:1};
}
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
_doc.body.removeChild(iframe);
_doc.body.removeChild(form);
//执行回调方法
if(typeof fn === "function"){
fn(json);
}else if(arr = url.match(/[&?]callback=([^&#]+)/)){
typeof window[arr[1]] === 'function' && fn(json);
}
}
}
}

使用时直接调用:

1
2
3
postJSONP("http://libo.sina.com.cn:3000/t_post1",postData,function(data){
console.log(data);
});

iframe方式实现跨域虽然貌似有良好的兼容性,但是配置和调试都非常的麻烦,而且不确定因素有很多,不是可靠的跨域实现方式。

关于cookie跨域携带

cookie传递在跨域时兼容问题比较多,

  • 使用CORS跨域传递cookie无法兼容ie,而且会让接口跨域无法设置通配符,在移动网站开发中可以使用,pc站无法使用。
  • 天猫之前的方案是淘宝单独出一个接口使用jsonp获取cookie,post提交则直接把cookie内容写在post的body中直接提交。
  • 使用iframe进行post提交可以跨域,但调试和配置比较复杂。

出于安全考虑敏感信息尽量不要放在cookie中。当使用post进行跨域提交时需要使用jwt等手段对敏感信息进行加密。


用到的js插件

1
2
3
4
5
6
├── Base64.js
├── jQuery-ajaxTransport-XDomainRequest.js
├── jquery-1.11.1.min.js
├── jquery.placeholder.js
├── md5.js
└── sweetalert.min.js

其他兼容坑

  1. ie8,9无法传递Referer【服务端关闭对ie的Referer验证】
  2. ie下用插件设置placeholder
  3. less编译时无法解析”/9”hack写法.
  4. 在es6转为es5时默认严格模式,一些写法ie11下会报错,用ts工具编译时需增加配置项:noImplicitUseStrict: true

参考引用

其他

markdown表格制作网站(墙外)
http://www.tablesgenerator.com/markdown_tables#


id 跨域方案 get请求 post请求 兼容ie8 复杂请求 携带cookie 父子域名 完全跨域 配置复杂度 调试友好度 安全性
1 CORS X √* 2 5 5
2* CORS+xdomain X X 4 4 3
3 jsonp X X 1 3 2
4 document.domain+iframe X X 5 0 2
5 window.name+iframe X 5+ 0 2