浏览器同源策略及 Ajax 跨域解决方案

因为在开发过程中会经常遇到因为浏览器同源策略而导致的跨域问题,而多数开发者对浏览器同源策略和跨域问题并没有很清晰的认识,所以打算在这篇文章中说下浏览器同源策略和我们最经常会遇到的 Ajax 跨域问题及其解决方案。

对于源的定义,MDN 中是这么解释的:如果两个页面的协议、域名和端口都相同,则两个页面具有相同的源。

从定义我们可以知道,关注两个页面是否同源,只要比较两个页面的协议域名端口即可。

举个例子,假设有以下页面,比较 A 页面与其它页面是否同源~

A:http://xys.ttsy/a.html 
B:http://xys.ttsy/b.html 
C:https://xys.ttsy/c.html 
D:http://d.xys.ttsy/d.html 
E:http://xys.ttsy:8081/e.html 

根据定义,可以知道 A 和 B 同源,而 A 和 C、D、E 不同源。A、B 页面同源是因为其协议(都是 http)、域名(都是 xys.ttsy)和端口(都是 80)都相同;而 A 与 C、D、E 不同源,是因为 A 和 C 不同协议(http 和 https),A 和 D 不同域名(xys.ttsy 和 d.xys.ttsy),A 和 E 不同端口(80 和 8081) 。

在浏览器中,一个最核心也最基本的安全功能便是同源策略。

同源策略是指浏览器中一个源的脚本只能访问同源的另一个脚本的策略。

也就是说,在浏览器中的脚本如果要访问其它脚本的话,那么两个脚本必须是同源的,否则会受到浏览器同源策略的限制。如果两个脚本非同源,会有三个行为受到限制

  • DOM 无法获得;
  • Cookie、LocalStorage 和 IndexDB 无法共享;
  • Ajax 请求限制;
DOM 无法获得

DOM 无法获得的限制最常见的是在 iframe 窗口与父窗口之间,如果父窗口与其 iframe 窗口的脚本是不同源的,则它们互相无法获取对方的 DOM 元素。

如下父窗口与 iframe 不同源,父窗口中无法获取 iframe 窗口中脚本的 DOM 元素

<iframe id="myIframe" src="http://www.taobao.com" width="500" height="500" ></iframe>
<script type="text/javascript">
    var myIframe = document.getElementById('myIframe').contentWindow.document;
    console.log(myIframe)  // 能获取到 document 对象,但里面没有有效数据
    console.log(myIframe.body)  // 空的 body
</script>

上述代码打印出来的效果如下所示

无效的 document 和 body

同理,在 iframe 窗口脚本中也无法获取父窗口脚本的 DOM 元素

console.log(self.parent.document)
console.log(self.top.document)
console.log(self.parent.document.body)
console.log(self.top.document.body)

那么,有没有什么方式能够规避此类同源策略的限制呢?答案是有的,只是这其中也是有条件的。

当父窗口与 iframe 窗口一级域名相同而二级域名不同的时候(或者说有共同的一级域名更为准确一点),则可以通过设置 document.domain 属性来规避此类同源策略的限制。

只要分别在两个窗口中对应的脚本文件中设置如下代码即可

document.domain='ttsy.com';  // 设置同一个一级域名
Cookie、LocalStorage 和 IndexDB 无法共享

若两个脚本不同源,则 Cookie、LocalStorage 和 IndexDB 的内容无法共享。

对于 Cookie 来说,有两种方式可以规避此类同源策略的限制。

若是一级域名相同而二级域名不同的情况(或者说有共同的一级域名更为准确一点),则可以通过设置 document.domain 属性来规避此类同源策略的限制。

只要两个脚本文件设置相同的 document.domain 值,即可共享 Cookie 。

document.domain='ttsy';  // 设置相同的值

第二种方式则是服务器端代码在设置 Cookie 的时候,将 domian 属性设置为一级域名,那么该一级域名下的子域名同样可以共享 Cookie 。

而 LocalStorage 和 IndexDB 则无法通过上述方法来规避同源策略。

而对于完全不同源的页面来说,还可以通过 window.name、window.postMessage 等方式来实现通讯,本篇不继续赘述,有兴趣的童鞋可以查阅相关资料了解。

Ajax 请求限制

在一个脚本中,如果通过 Ajax 请求另一个非同源的脚本,则会报错。报错信息经常会类似下面酱紫

XMLHttpRequest cannot load xxx  . No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

这即是我们经常提到的 Ajax 跨域问题,这是由于浏览器的同源策略引起的,Ajax 无法请求非同源的资源。

对于 Ajax 跨域解决方案,通常来说有如下三种:

  • JSONP
  • CORS
  • 代理服务器

下面就详细描述上述三种 Ajax 跨域解决方案。

JSONP

JSONP 是解决跨域很常用的一种方法了,其原理是通过 <script> 标签向服务器端发起请求不受浏览器同源策略的限制。而服务器端在接受到请求后,可以返回一个指定名字的函数,该函数的参数是需要返回的数据。

举个例子吶~

var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = 'http://ttsy.com/getName?callback=fn';
document.body.appendChild(script);
    
function fn(data) {
    console.log('name: ' + data.name);
};

上述代码通过创建一个 script 标签,将其 src 属性设置为执行请求的 url 并将其插入到页面中,向服务器发起一个请求。上述代码中请求的 url 为 http://ttsy.com/getName?callback=fn ,指定了回调函数名为 fn 。

而服务端在收到该请求后返回一个指定名字的函数,该函数的参数是需要返回的数据。服务端返回数据如下

 fn({
        "name": "ttsy"
    });

由于通过 script 标签请求到的数据会直接执行,所以上述服务端返回数据后会直接执行 fn 函数,所以在上述代码中 fn 函数将会输出 name:ttsy 。

基于 JSONP 的实现原理,其只能通过 get 请求来获得数据,不能进行较为复杂的 post 请求,所以在更多的情况下,我们会采用下面的 CORS 来解决 Ajax 的跨域问题。

CORS

CORS(Cross-origin resource sharing),全称是跨域资源共享。它允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。是 Ajax 跨域问题的根本解决方案。

CORS 的实现需要浏览器与服务器共同支持。

目前来说,基本所有的浏览器都支持 CORS 功能,当浏览器发现 Ajax 跨域请求时,会在 HTTP 请求头添加一些附加的头信息,有时会多出一次 HTTP 请求,这些都是由浏览器自动完成的。

而在服务器中要实现了 CORS 功能,则需要设置 Access-Control-Allow-Origin 属性。

Access-Control-Allow-Origin: *
代理服务器

代理服务器是指通过设置一个同源的代理服务器,然后将我们的 Ajax 请求发送到我们的代理服务器上,再通过代理服务器去向实际的服务器去请求数据,最后通过代理服务器返回给浏览器。

通过设置代理服务器来规避浏览器同源策略的限制,其原理是服务器之间的资源请求并没有同源策略的限制。但是由于操作起来并不方便,所以在实际开发中并不会经常用这种方法来解决问题。

----- 未发布部分 -----
还有 document.domain、postMessage

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容