同domain(或ip),同端口,同协议视为同一个域,一个域内的脚本仅仅具有本域内的权限,可以理解为本域脚本只能读写本域内的资源,而无法访问其它域的资源。这种安全限制称为同源策略。

出于安全考虑,HTML的同源策略不允许JavaScript进行跨域操作, 直接发送跨域 AJAX 会得到如下错误:

XMLHttpRequest cannot load http://b.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://a.com' is therefore not allowed access.

随着Web App的功能越来越强 各种跨域的需求催生了无数的跨域手法。甚至在HTML5标准中都给出了官方的跨域方法, 也是最近应付面试的需要,拿一篇文章来总结既有的各种跨域手段。

这些跨域通信的方法大致可以分为两类:

  • 一类是Hack,比如通过title, navigation等对象传递信息,JSONP可以说是一个最优秀的Hack。
  • 另一类是HTML5支持,一个是Access-Control-Allow-Origin响应头,一个是window.postMessage。
设置 document.domain
  • 原理:相同主域名不同子域名下的页面,可以设置document.domain让它们同域
  • 限制:同域document提供的是页面间的互操作,需要载入iframe页面

下面几个域名下的页面都是可以通过document.domain跨域互操作的: http://a.com/foo, http://b.a.com/bar, http://c.a.com/bar。 但只能以页面嵌套的方式来进行页面互操作,比如常见的iframe方式就可以完成页面嵌套:

// URL http://a.com/foo
var ifr = document.createElement('iframe');
ifr.src = 'http://b.a.com/bar';
ifr.onload = function(){
var ifrdoc = ifr.contentDocument || ifr.contentWindow.document;
ifrdoc.getElementsById("foo").innerHTML);
}; ifr.style.display = 'none';
document.body.appendChild(ifr);

上述代码所在的URL是http://a.com/foo,它对http://b.a.com/bar的DOM访问要求后者将 document.domain往上设置一级:

// URL http://b.a.com/bar
document.domain = 'a.com'

document.domain只能从子域设置到主域,往下设置以及往其他域名设置都是不允许的, 在Chrome中给出的错误是这样的:

Uncaught DOMException: Failed to set the 'domain' property on 'Document': 'baidu.com' is not a suffix of 'b.a.com'
有src的标签
  • 原理:所有具有src属性的HTML标签都是可以跨域的,包括<img>, <script>
  • 限制:需要创建一个DOM对象,只能用于GET方法

在document.body中append一个具有src属性的HTML标签, src属性值指向的URL会以GET方法被访问,该访问是可以跨域的。

其实样式表的标签也是可以跨域的,只要是有src或href的HTML标签都有跨域的能力。

不同的HTML标签发送HTTP请求的时机不同,例如在更改src属性时就会发送请求,而script, iframe, link[rel=stylesheet]只有在添加到DOM树之后才会发送HTTP请求:

var img = new Image();
img.src = 'http://some/picture'; // 发送HTTP请求 var ifr = $('<iframe>', {src: 'http://b.a.com/bar'});
$('body').append(ifr); // 发送HTTP请求
JSONP
  • 原理:<script>是可以跨域的,而且在跨域脚本中可以直接回调当前脚本的函数。
  • 限制:需要创建一个DOM对象并且添加到DOM树,只能用于GET方法

    JSONP利用的是<script>可以跨域的特性,跨域URL返回的脚本不仅包含数据,还包含一个回调:
// URL: http://b.a.com/foo
var data = {
foo: 'bar',
bar: 'foo'
};
callback(data);

该例子只用于示例,实际情况应当考虑名称隐藏等问题。

然后在我们在主站http://a.com中,可以这样来跨域获取http://b.a.com的数据:

// URL: http://a.com/foo
var callback = function(data){
// 处理跨域请求得到的数据
};
var script = $('<script>', {src: 'http://b.a.com/bar'});
$('body').append(script);

其实jQuery已经封装了JSONP的使用,我们可以这样来:

$.getJSON( "http://b.a.com/bar?callback=callback", function( data ){
// 处理跨域请求得到的数据
});

$.getJSON与$.get的区别是前者会把responseText转换为JSON,而且当URL具有callback参数时, jQuery将会把它解释为一个JSONP请求,创建一个<script>

和所有依赖于创建HTML标签的方式一样,JSONP也不支持POST,而GET的数据是放在URL里的。一般来讲URL限长是在2000字符左右。

在如何理解HTTP响应的状态码?一文中有更多关于HTTP响应状态码的讨论。

navigation 对象
  • 原理:iframe之间是共享navigator对象的,用它来传递信息
  • 要求:IE6/7

有些人注意到了IE6/7的一个漏洞:iframe之间的window.navigator对象是共享的。 我们可以把它作为一个Messenger,通过它来传递信息。比如一个简单的委托:

// a.com
navigation.onData(){
// 数据到达的处理函数
}
typeof navigation.getData === 'function'
|| navigation.getData()
// b.com
navigation.getData = function(){
$.get('/path/under/b.com')
.success(function(data){
typeof navigation.onData === 'function'
|| navigation.onData(data)
});
}

与document.navigator类似,window.name也是当前窗口所有页面所共享的。也可以用它来传递信息。 同样蛋疼的办法还有传递Hash(有些人叫锚点),这是因为每次浏览器打开一个URL时,URL后面的#xxx部分会保留下来,那么新的页面可以从这里获得上一个页面的数据。

跨域资源共享(CORS)
  • 原理:服务器设置Access-Control-Allow-OriginHTTP响应头之后,浏览器将会允许跨域请求
  • 限制:浏览器需要支持HTML5,可以支持POST,PUT等方法

前面提到的跨域手段都是某种意义上的Hack, HTML5标准中提出的跨域资源共享(Cross Origin Resource Share,CORS)才是正道。 它支持其他的HTTP方法如PUT, POST等,可以从本质上解决跨域问题。

例如,从http://a.com要访问http://b.com的数据,通常情况下Chrome会因跨域请求而报错:

XMLHttpRequest cannot load http://b.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://a.com' is therefore not allowed access.

错误原因是被请求资源没有设置Access-Control-Allow-Origin,所以我们在b.com的服务器中设置这个响应头字段即可:

Access-Control-Allow-Origin: *              # 允许所有域名访问,或者
Access-Control-Allow-Origin: http://a.com # 只允许XXX域名访问

为 xhr 设置 withCredentials 后 CORS 方法跨域还可 携带Cookie,但 PUT/POST 请求需要注意处理 preflight 请求。

window.postMessage
  • 原理:HTML5允许窗口之间发送消息
  • 限制:浏览器需要支持HTML5,获取窗口句柄后才能相互通信

    这是一个安全的跨域通信方法,postMessage(message,targetOrigin)也是HTML5引入的特性。

可以给任何一个window发送消息,不论是否同源。第二个参数可以是*但如果你设置了一个URL但不相符,那么该事件不会被分发。看一个普通的使用方式吧:

// URL: http://a.com/foo
var win = window.open('http://b.com/bar');
win.postMessage('Hello, bar!', 'http://b.com');
// URL: http://b.com/bar
window.addEventListener('message',function(event) {
console.log(event.data);
});

注意IE8及小于IE8的版本不支持addEventListener,需要使用attachEvent来监听事件。 参见:事件处理中的this:attachEvent, addEventListener, onclick

访问控制安全的讨论

在HTML5之前,JSONP已经成为跨域的事实标准了,jQuery都给出了支持。 值得注意的是它只是Hack,并没有产生额外的安全问题。 因为JSONP要成功获取数据,需要跨域资源所在服务器的配合,比如资源所在服务器需要自愿地回调一个合适的函数,所以服务器仍然有能力控制资源的跨域访问。

跨域的正道还是要使用HTML5提供的CORS头字段以及window.postMessage, 可以支持POST, PUT等HTTP方法,从机制上解决跨域问题。 值得注意的是Access-Control-Allow-Origin头字段是资源所在服务器设置的, 访问控制的责任仍然是在提供资源的服务器一方,这和JSONP是一样的。

Web开发中跨域的几种解决方案的更多相关文章

  1. python web开发中跨域问题的解决思路

    线上环境不存在跨域问题,nginx转发 解决思路: 1.什么是跨域 在浏览器窗口中,和某个服务端通过某个 “协议+域名+端口号” 建立了会话的前提下,去使用与这三个属性任意一个不同的源提交了请求,那么 ...

  2. Web开发必知的八种隔离级别

    ACID性质是数据库理论中的奠基石,它定义了一个理论上可靠数据库所必须具备的四个性质:原子性,一致性,隔离性和持久性.虽然这四个性质都很重要,但是隔离性最为灵活.大部分数据库都提供了一些可供选择的隔离 ...

  3. 解决vue-cli项目开发中跨域问题

    一.开发环境中跨域 使用 Vue-cli 创建的项目,开发地址是 localhost:8080,需要访问非本机上的接口http://10.1.0.34:8000/queryRole.不同域名之间的访问 ...

  4. java web开发中遇到的问题及解决方案(个人学习日志,持续更新)

    转:http://blog.csdn.net/ducexu/article/details/7529613 2012.05.02   星期三 1.问题:导入的新工程,名字上出现感叹号. 原因:工程的j ...

  5. Vue项目中跨域的几种方式

    经常使用vue + webpack搭建项目,但在请求某些json数据时存在跨域问题,此时有几种修改方法 1. 修改后台header, 但如果只是请求外部数据,是没法修改后台配置的 header('Ac ...

  6. Ajax 跨域的几种解决方案

    作者:黄轩链接:http://www.zhihu.com/question/19618769/answer/38934786来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处 ...

  7. React中跨域问题的完美解决方案

    针对react版本^16.6.0有多种解决方案 方案一:package.json中加上proxy代理配置 在packge.json加入 "proxy": "http:// ...

  8. Web开发跨域问题

    什么是域?    协议,  ip(域名). 端口 前端:域  后端:域   js 进行跨域请求, 因为浏览器的同源策略,导致了两个不同域请求出错 浏览器 会尝试向后端发送 option 请求, --- ...

  9. java web开发跨域问题

    分布式环境,前后端分离背景下跨域问题 1.1 设置页面document.domain去把2个页面之间的跨域交互统一 一级域名相同的情况下 调用者和页面提供者进行一个协调 页面提供者要在document ...

随机推荐

  1. ceph rbd 入门

    1.一个现成的ceph cluster 参考之前写的ceph-deploy 部署ceph cluster 2.配置client与ceph cluster对接 在ceph cluster的管理节点上安装 ...

  2. noip模拟赛 戏

    [问题背景]zhx 和他的妹子(们) 做游戏.[问题描述]考虑 N 个人玩一个游戏,任意两个人之间进行一场游戏(共 N*(N-1)/2 场),且每场一定能分出胜负.现在, 你需要在其中找到三个人构成“ ...

  3. jQuery下拉列表操作(转)

    转地址:http://www.cnblogs.com/yaoshiyou/archive/2010/08/24/1806939.html jQuery获取Select选择的Text和Value:语法解 ...

  4. python_swift_project_swift使用

    1. swift的存取用curl命令. 我们先把pub url 和token保存起来 root@A071103070098:~# export pubURL=http://10.194.148.102 ...

  5. Linux经常使用命令-文件搜索命令-文件搜索命令find

    命令名称:find 命令所在路径:/bin/find 语法:find [搜索范围] [匹配条件] 功能描写叙述:文件搜索 演示样例 find /etc - name init 在文件夹/etc 中查找 ...

  6. 喜欢玩warcraft的ltl

    喜欢玩warcraft的ltl 时间限制:2000 ms  |  内存限制:65535 KB 难度:3 描写叙述 ltl 很喜欢玩warcraft.由于warcraft十分讲究团队总体实力,而他自己如 ...

  7. C++之:友元类

    一.文章来由 上一篇写了友元函数,这一次写一个姊妹篇,继续深入探究一下友元类. 二.定义 友元类的全部成员函数都是还有一个类的友元函数.都能够訪问还有一个类中的隐藏信息(包含私有成员和保护成员). 当 ...

  8. 读书笔记-APUE第三版-(7)进程环境

    本章关注单进程执行环境:启动&终止.參数传递和内存布局等. 进程启动终止 如图所看到的: 启动:内核通过exec函数执行程序,在main函数执行之前.会调用启动例程(start-up rout ...

  9. Android开发之使用sqlite3工具操作数据库的两种方式

    使用 sqlite3 工具操作数据库的两种方式 请尊重他人的劳动成果,转载请注明出处:Android开发之使用sqlite3工具操作数据库的两种方式 http://blog.csdn.net/feng ...

  10. ZOJ 2397:Tian Ji -- The Horse Racing

    Tian Ji -- The Horse Racing Time Limit: 5 Seconds      Memory Limit: 32768 KB Here is a famous story ...