在浏览器中,有一个很重要的安全性限制,被称为 “Same-Origin Policy”(同源策略)。通过 XHR 实现 Ajax 通信的一个主要限制,就来自与同源策略。即默认情况下,XHR 对象只能访问与包含它的页面位于同一个域中的资源。然而,当进行一些比较深入的前端编程的时候,不可避免地需要进行跨域操作。这时候“同源策略”就显得过于苛刻,于是,就需要进行跨域访问数据了!
一、同源策略?
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能。所谓同源就是指两个页面拥有相同的协议(protocol),主机 IP*(域名)和端口(如果指定),那么这两个页面就属于同一个源(origin)*。
下图说明了大部分的同源策略检测机制的情况,可做参考:
二、为什么要有同源策略?
举个栗子:
比如一个黑客程序,他利用 <iframe>
把真正的银行登录页面嵌到他的页面上。当你使用真实的用户名、密码登录时,他的页面就可以通过 Javascript 读取到你的表单中 input
中的内容。这样用户名、密码就轻松到手了。
同源策略就为了防止上述事情的发生。如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
三、如何跨域?
同源策略控制了不同源之间的交互,例如在使用 XMLHttpRequest ,则会受到同源策略的约束。
浏览器同源策略并不是对所有的请求均制约:
- 制约:XMLHttpRequest ;
- 不制约:
<img>
、<iframe>
、<script>
等具有 src 属性的标签;
处理 Ajax 跨域问题主要有以下 4 种方式:
- 利用 JSONP
- 利用
<iframe>
- 利用代理
- 跨域资源共享(CORS)
1. JSONP
在 HTML DOM 中,<script>
标签是可以跨域访问服务器上的数据的。因此,可以指定 <script>
的 src 属性为跨域的 url,从而实现跨域访问。利用在页面中创建 <script>
节点,向不同域提交 Http 请求的方法称为 JSONP,这项技术可以解决跨域提交 Ajax 请求的问题。
下面,我们来看一下示例代码:
前端代码:
function doResponse(data){ // 数据请求成功时的回调
console.log('开始处理响应数据');
console.log(data);
}
$('#bt3').click(function(){ //JSONP方式
var script = document.createElement('script');
script.type = "text/javascript";
script.src="http://localhost/AJAX_day08/5.php?callback=doResponse";
document.head.appendChild(script);
});
服务器端代码:
header('Content-Type: application/javascript'); // 相应的请求头
$cb = $_REQUEST['callback'];
$arr = ['ename'=>'King', 'age'=>50];
$str = json_encode($arr);
echo $cb.'(' .$str. ')' ; // 利用传过来的函数名作为回调,将所需数据作为参数传入
顺便分享下,jQuery 中两个可以发起 JSONP 请求的函数:
$.getJSON()
:- 普通 XHR 请求:
$.getJSON('x.php', data, fn);
- JSONP 请求:
$.getJSON('x.php?callback=?',data, fn);
- 普通 XHR 请求:
$.ajax();
:普通 XHR 请求:
$.ajax({ url: 'x.php', data: {'uname':'tom'}, success: fn });
JSONP 请求:
$.ajax({ url: 'x.php', data: {'uname':'tom'}, dataType: 'jsonp', // 指定服务器端返回jsonp的数据的类型 success: fn });
JSONP 的优点:
- 不像 XMLHttpRequest 对象实现的 Ajax 请求那样受到同源策略的限制;
- 兼容性更好,在更加古老的浏览器中都可以运行,不需要 XMLHttpRequest 或 ActiveX 的支持;
- 在请求完毕后,可以通过调用 callback 的方式回传结果。
JSONP 的缺点:
- 只支持 GET 请求而不支持 POST 等其它类型的 Http 请求;
- 它只支持跨 Http 协议请求的域,不能解决不同协议的域的两个页面之间如何进行 JavaScript 调用的问题。
2. <iframe>
和 location.hash
原理是利用 location.hash 来进行传值,因为改变 hash 并不会导致页面刷新,所以可以利用 hash 值来进行数据传递。
注释:在 url:http://a.com#helloword
中的 #helloworld 就是 location.hash。
举个栗子:
假设域名 a.com 下的文件 cs1.html 要和 cnblogs.com 域名下的 cs2.html 传递信息。
首先 cs1.html 自动创建一个隐藏的 <iframe>
。<iframe>
的 src 指向 cnblogs.com 域名下的 cs2.html 页面,这时的 hash 值可以做参数传递用。
cs2.html 响应请求后,再将通过修改 cs1.html 的 hash 值来传递数据(由于两个页面不在同一个域下,IE、Chrome 不允许修改 parent.location.hash 的值,所以要借助于 cnblogs.com 域名下的一个代理 <iframe>
;Firefox 可以修改)。
同时在 cs1.html 上加一个定时器,隔一段时间来判断 location.hash 的值有没有变化。一点有变化,则获取 hash 值。代码如下:
先是 a.com 下的文件 cs1.html 文件:
function startRequest(){
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo';
document.body.appendChild(ifr);
}
function checkHash() {
try {
var data = location.hash ? location.hash.substring(1) : '';
if (console.log) {
console.log('Now the data is '+data);
}
} catch(e) {};
}
setInterval(checkHash, 2000);
再是 cnblogs.com 域名下的 cs2.html:
switch(location.hash){ // 模拟一个简单的参数处理操作
case '#paramdo':
callBack();
break;
case '#paramset':
//do something……
break;
}
function callBack(){
try {
parent.location.hash = 'somedata';
} catch (e) {
// ie、chrome的安全机制无法修改parent.location.hash,
// 所以要利用一个中间的cnblogs域下的代理iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
// 注意该文件在"a.com"域下
ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata';
document.body.appendChild(ifrproxy);
}
}
最后 a.com 下的域名 cs3.html:
//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
<iframe>
的优点:- 可以解决完全跨域情况下的脚本置换问题;
<iframe>
的缺点:- 传递的数据容量、类型都是有限的;
- 数据直接暴露在了 url 中,不安全。
3. 代理
原理:用户访问 A 网站时所产生的对 B 网站的跨域访问请求,均提交到 A 网站的代理页面( Post 页面过去)。由该页面代替用户页面完成交互,从而返回合适的结果。
代理的优点:
- 可以解决现阶段所能够想到的多数跨域访问问题;
代理的缺点:
- 要求 A 网站提供 Web 代理的支持。因此 A 网站与 B 网站之间必须是紧密协作的;
- 每次交互过程,A 网站的服务器负担增加;
- 无法代用户保存 session 状态。
4. 跨域资源共享(CORS)
新版本的 XMLHttpRequest 对象,可以向不同域名的服务器发出 Http 请求。这叫做”跨域资源共享”(Cross-origin resource sharing,简称CORS)。CORS背后的基本思想,就是使用自定义的 Http 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
举个栗子:
一个简单的使用 GET 或 POST 发送的请求,它没有自定义的头部,而主题内容是 text/plain
。在发送该请求时,需要给他附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予相应。比如:
Origin: http://www.nczonline.net
如果,服务器认为这个请求可以接受,那么就在 Access-Control-Allow-Origin
头部中回发相同的源信息(如果是公共资源,可以回发 *
)。以上述例子为例,服务器端则要进行这些设置:
header('Access-Control-Allow-Origin: http://www.nczonline.net'); // 上述例子
header('Access-Control-Allow-Origin:*'); // 公共资源
header('Access-Control-Allow-Methods:POST,GET');
如果没有这个头部,或者有这个头部但源信息不匹配,浏览器就会驳回请求。
跨域资源共享(CORS)的优点:
- 代码的写法与不跨域的请求完全一样。
跨域资源共享(CORS)的缺点:
- 浏览器必须支持这个功能,且服务器端必须同意这种”跨域”。
- 请求和响应都不包含 cookie 信息。