前端跨域问题的起因是什么—同源政策
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 详解 - 阮一峰
网友评论