本文目的
今天终于梳理清楚了AJAX跨站请求的原理和解决方案,在此记录下,作为备忘。
浏览器同源策略
同源策略又名同域策略是浏览器中的主要安全措施。这里的“源”指的是主机名、协议和端口号的组合;我们可以把一个“源”看作是某个web页面或浏览器所浏览的信息的创建者。 同源策略,简单地说就是要求动态内容(例如,JavaScript或者VBScript)只能阅读与之同源的那些HTTP应答和cookies,而不能阅读来自不同源的内容。更为有趣的是,同源策略对写操作没有任何限制。因而,一个web站点可以向任何其他的Web站点发送(或写入)HTTP请求,尽管为 了防止跨站请求可能会对发送这些请求有关的cookies和头部有所限制。(以上描述来之网络)
通俗的讲:同源策略就浏览器不允许是站点A的js脚本访问站点B提供的动态数据,因为这样可能带来安全隐患。
如何跨域
跨域就是绕过同源策略限制,使得站点A的js脚本可以访问到站点B提供的数据。怎么绕过呢?比较常见跨域方案是使用代理,即在A站点下搭建一个代理(可以是个自己的CGI),让代理访问B站点,并将结果反给A页面,这种方法的可扩展性不够好,实施成本大。目前比较经典的方案使用JSONP方式(json with padding)。从上面的定义中,可以知道浏览器不允许A的js脚本访问B提供的数据,但是没有阻止拉取B站点提供的静态资源,如js文件,图片等。所以,跨域的关键在于如何利用好这些“静态”的内容。举个例子:
A站点需要访问B站点的数据,B站点有下面的脚本(PHP编写的cgi):
1
2
3
|
<?php
echo json_encode(array(‘msg’=>’cross domain access’));
?>
|
这样是无法拿到数据的,因为B站点吐出的是json数据,如下:
1
|
{“msg”:”cross domain access”}
|
但如果将B站点的脚本改成下面的样子,站点A就可以访问到数据。
1
2
3
|
<?php
echo ‘localFunc(’. json_encode(array(‘msg’=>’cross domain access’)). ‘);’;
?>
|
因为此时,返回的结果如下:
1
|
localFunc({“msg”:”cross domain access”});
|
没错,是一个函数调用,可以理解为脚本,而不是数据,浏览器认为合法,所以允许访问。当然,localFunc函数需要在A站点的页面定义。
script标签跨域
上面描述了原理,接下来描述具体如何实时。首先,我们需要搭建两个站点,我的实验环境是LAMP,所以在apache中设置了两个virtual host: http://a.oa.com和http://b.oa.com。
搭建好后,将文件index.html拷贝到站点A根目录
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
|
<!
DOCTYPE
html>
<
html
>
<!--A站点首页 index.html-->
<
head
>
<
meta
http-equiv="Content-Type" content="text/html; charset=UTF-8">
<
title
>cross domain demo</
title
>
</
head
>
<
body
>
<
input
type="button" value="JSONP跨站原理" onclick="onClickCrossJSONPTheory();"></
input
><
br
/>
<
input
type="button" value="JSONP跨站jquery简化后版本" onclick="onClickCrossJSONP();"></
input
><
br
/>
<
input
type="button" value="getScript跨站" onclick="onClickJQueryScript();"></
input
><
br
/>
</
body
>
<
script
>
function localFunc(data) {
alert(data.msg);
}
function onClickCrossJSONPTheory() {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "http://b.oa.com/index.php?callback=localFunc";
document.getElementsByTagName('head')[0].appendChild(script);
// 省略删除
}
function onClickCrossJSONP() {
$.getJSON('http://b.oa.com/index.php?callback=?', function(json) {
alert(json.msg);
});
}
function onClickJQueryScript() {
$.getScript('http://b.oa.com/alert.js', function(){
cross();
cross();
});
}
</
script
>
</
html
>
|
将index.php文件拷贝到B站点根目录
1
2
3
4
|
<?php
// B站点首页: index.php
echo
$_GET
[
'callback'
] .
'('
. json_encode(
array
(
'msg'
=>
'hello cross-domain'
)) .
');'
;
?>
|
为A站点和B站点配上host(很重要),打开浏览器,输入“http://a.oa.com”,得到如下页面:
点击第一个按纽会弹出对话框,答应站点B的内容,如下:
我们看看index.html中都做了些什么:
1. 定义回调函数localFunc
2. 在onClickCrossJSONPTheory函数中动态加载script标签
3. 将localFunc作为url参数
4. 清理创建的标签(省略),如果不清理,script标签会越来越多。
而B站点将传过来的回调函数包装上(padding)自己的数据(JSON),返回给A站点。
这么简单,就“骗”过了浏览器,不是吗?
$.getJSON跨域
上面的步骤是不是有点麻烦,除了自定义函数需要根据不同业务逻辑而变化,其他步骤全部都是一样,所以这些基本上可以提取出来重用。
好在jquery提供了这样的机制。点击页面上的第二个按钮,实现了同样的功能,但是用了3行代码(onClickCrossJSONP函数中的实现),其他的工作jquery帮我们做了。我们只需要在$.getJSON的url参数中添加一个回调参数,值用问号“?”代替,如下:
这样jquery框架会自动填充一个唯一的函数名称,也就是后面匿名回调函数的名称,http抓包显示如下:
$.getScript跨域
最后,捎带说明一下$.getScript函数,用法与getJSON类似,只不过它可以直接和js文件交互,而不是cgi,列子如上,需要在B站点根目录下添加alert.js文件,如下:
1
2
3
4
5
6
7
|
// 定义函数
function
cross() {
alert(
'cross domain execute'
);
}
// 调用函数
cross();
|
点击A站点首页的第三个按钮,可以发现弹出了三次对话框,说明getScript加载js后,会执行它,然后回调函数的中也可以调用相关函数和对象。
总结
- 浏览器同源策略是基于安全的考虑强制执行
- 跨域方法:1)代理; 2)JSONP两种方式进行跨域操作(推荐)
- jquery框架的$.getJSON可以简化JSONP的实现过程
- JSONP需要CGI做出调整,返回的数据必须是js代码(函数调用),而不是json数据。
参考资料