前端跨域问题总结

前端跨域问题的起因是什么—同源政策

1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。
最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同"。协议相同、域名相同、端口相同。

举例来说
http://shaocx.com/index.html 这个网址协议是http://,域名是www.example.com,端口是80(默认端口可以省略)。
它的同源情况如下。
http://shaocx.com/index2.html 同源
https://shaocx.com/index2.html 不同源(协议不同)
http://www.shaocx.com/index.html 不同源(域名不同)
http://shaocx.com:81/index.html 不同源(端口不同)

同源政策的目的

同源策略有助于保护使用经过验证会话的网站。下面是一个如果没有同源政策会出现的例子。

假设用户正在访问一个银行网站并且没有注销。然后用户打开了一个有恶意JavaScript代码的网站,该网站向银行网站请求数据。由于用户没有注销银行网站上的账号,恶意代码在银行网站上做任意事情。例如,它可以获取用户最近一次交易的列表,或者创建一个新交易等。

这主要是因为浏览器会根据请求的域名,自动加上之前存储在该域名底下的cookie。

而无意间访问恶意网站的用户,会期望他或她访问的网站无法访问银行会话cookie。尽管JavaScript没有直接访问银行会话cookie,但它仍然可以通过银行网站的会话cookie向银行网站发送和接收请求。由于脚本基本上可以和用户做的一样,所以银行网站的CSRF保护也不会有效。

由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。

同源政策限制范围

1、 Cookie、LocalStorage 和 IndexDB 无法读取。
2、 DOM 无法获得。
3、 AJAX 请求不能发送。
4、 window对象无法获取。

Cookie

Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,当两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。

1、前端自己存取cookie时
A网页是http://w1.shaocx.com/a.html
B网页是http://w2.shaocx.com/b.html
document.cookie = "try1=1;domain=shaocx.com";
这样cookie被设在顶级域名 .shaocx.com 上,两个页面都可以读取到。

2、后端添加cookie时
服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.shaocx.com。
Set-Cookie: key=value; domain=.shaocx.com; path=/

这里有一些地方需要注意:
1、挂载在顶级域名上的是这种格式 .a.com ,前端在设置、书写的时候,前面的 . 是不需要书写的。但是在后端加上的时候,这个 . 又是必须的。
2、cookie在读取的时候,只能读取到key和value。(path、host、expires不能读取)
3、cookie在读取时,如果你在多个域名下都设置了,那么会出现多个cookie。
4、多个cookie时读取的值会根据你使用的方法有所不同。(我这边的Cookies使用了js-cookie.js
document.cookie会读取到所有可以读取到的cookie。而且是根据先后插入的顺序进行读取。
Cookies.get()会读取先插入的值(根据document.cookie的顺序)。
Cookies.getJSON()会优先读取后插入的值(转换成对象后值被覆盖)。

iframe

iframe窗口和window.open方法打开的窗口一样,如果不同源,它们与父窗口无法通信。

先来回顾一下iframe之间互相通信的方法。
父子iframe互相操作
子iframe获取父iframe,parent.window,top.window
父iframe获取子iframe,document.getElementById("a").contentWindow

跨域会存在以下问题
window对象无法读取具体的对象值,只能读取系统默认方法。
window.location 可以设置,但不能读取。其它的 location 属性和方法被禁止访问;
document 不可以设置,也不能读取。
<iframe> 的 src 可以设置,也可以读取。但是在iframe中将自己的地址改变之后,iframe的src地址并不会变更。

iframe - document.domain

如果两个窗口根域名相同,那么设置上面介绍的document.domain属性,就可以规避同源政策,互相操作。

iframe - location.hash

location.hash是网页中#后面的部分,如果只是改变hash值,页面不会重新刷新。

// 父页面可以把信息写入已知地址的iframe的hash中。
document.getElementById('a').src = url + '#' + data;

// iframe中通过监听hashchange事件收到信息。
window.onhashchange = function () {
  var message = window.location.hash;
}

// iframe改变父页面的hash地址
parent.location.href = url + "#" + data;
iframe - window.name

window一个name属性,它最大特点是,无论是否同源都可以读取它。
父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性。
window.name = data;
接着,子窗口跳回一个与主窗口同域的网址。
location = ‘http://parent.url.com/xxx.html';
然后,主窗口就可以读取子窗口的window.name了。
var data = document.getElementById('myFrame').contentWindow.name;
这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。

// iframe页面
window.name = 'I was there!';    // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
                                 // 数据格式可以自定义,如json、字符串

// 父页面
var state = 0, 
iframe = document.createElement('iframe'),
loadfn = function() {
    if (state === 1) {
        var data = iframe.contentWindow.name;    // 读取数据
        alert(data);    //弹出'I was there!'
    } else if (state === 0) {
        state = 1;
        iframe.contentWindow.location = "http://a.com/proxy.html";    // 设置的代理文件
    }  
};
iframe.src = 'http://b.com/data.html';
iframe.onload  = loadfn;
document.body.appendChild(iframe);
iframe - postMessage

上面两种方法都属于破解,HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。
这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。这个方法可用在与window.open打开的新页面进行通讯,或者与iframe中的页面进行通讯。

// 这是iframe向父页面窗口发送消息。
window.parent.postMessage('Hello World!', 'http://bbb.com');
// 这是父页面向iframe窗口发送消息。
document.getElementById("a").contentWindow.postMessage('Nice to see you', 'http://aaa.com');

postMessage方法的第一个参数是string格式的具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口",设置该值后,只会向固定域名的页面发送信息,使用其他域名窗口打开当前iframe时,不会发送消息。也可以设为*,表示不限制域名,向所有窗口发送。
父窗口和子窗口都可以通过message事件,监听对方的消息。

window.addEventListener('message', function(e) {
  // data是数据,origin是域名。这边最好也判断一下,可以提升安全性
  console.log(e.data, e.origin);
});

localStorage

document.domain这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 无法通过这种方法,而要使用PostMessage API。

现有方案大多为使用postMessage与iframe组合来传递localStorage。
父页面监听子页面传来的data,然后渲染到localStorage中。再子页面监听父页面传来的data,渲染到自己的localStorage中。

// 这是父页面发送消息的代码
var data = JSON.stringify({key: 'storage', data: {name: ‘Jack’}});
document.getElementsByTagName('iframe')[0].contentWindow.postMessage(data, 'http://bbb.com');

// 这是子页面监听父页面发来的消息,并放置在localStorage中。
window.onmessage = function(e) {
  var payload = JSON.parse(e.data);
  localStorage.setItem(payload.key, JSON.stringify(payload.data));
};

子页面发送至父页面也同理。

AJAX

AJAX - 架设服务器代理

架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务)
比如nginx的反向代理可以将请求直接转发出去,并且接收返回值。

AJAX - JSONP

JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
首先,网页动态插入<script>元素,由它向跨源网址发出请求。

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}
window.onload = function () {
  addScriptTag('http://a.com/ip?callback=foo');
}
function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};

上面代码通过动态添加<script>元素,向服务器a.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。

// 服务器
foo({"ip": "8.8.8.8"});

由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。

AJAX - WebSocket

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

但是严格地说,WebSocket技术不属于HTML5,这个技术是对HTTP无状态连接的一种革新,本质就是一种持久性socket连接,在浏览器客户端通过javascript进行初始化连接后,就可以监听相关的事件和调用socket方法来对服务器的消息进行读写操作。与Ajax相比,Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以彼此相互推送信息;XHR受到域的限制,而WebSocket允许跨域通信,这个特性导致我们至少可以用来做远控。

WebSocket实现了全双工通信,使WEB上的真正的实时通信成为可能。浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在WebSocket协议中,为我们实现即时服务带来了三个好处:
1、客户端和服务器端之间数据传输时请求头信息比较小,大概2个字节。
2、服务器和客户端可以相互主动的发送数据给对方。
3、不需要多次创建TCP请求和销毁,节约宽带和服务器的资源。

具体可以参考
WebSocket 教程 - 阮一峰
跨域(二)——WebSocket

AJAX - CORS

CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

具体可以参考 跨域资源共享 CORS 详解 - 阮一峰

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

推荐阅读更多精彩内容

  • 前端开发中,跨域使我们经常遇到的一个问题,也是面试中经常被问到的一些问题,所以,这里,我们做个总结。小小问题,不足...
    Nealyang阅读 473评论 0 0
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    Yaoxue9阅读 1,301评论 0 6
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    HeroXin阅读 836评论 0 4
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    他方l阅读 1,064评论 0 2
  • 写笔记的页面和印象笔记如出一辙。 思路是大大改变了笔记的共享性和公开度。 有些像印象笔记和博客的结合体。
    慵懒的不倒翁阅读 298评论 0 0