前端跨域问题的起因是什么 - 同源政策
1995 年, 同源政策由 Netscape 公司引入浏览器. 目前, 所有浏览器都实行这个政策.
最初, 它的含义是指, A 网页设置的 Cookie,B 网页不能打开, 除非这两个网页 "同源". 所谓 "同源" 指的是 "三个相同". 协议相同, 域名相同, 端口相同.
举例来说
http://shaocx.com/index.html 这个网址协议是 http://, 域名是 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 https://github.com/js-cookie/js-cookie )
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 请求和销毁, 节约宽带和服务器的资源.
来源: http://www.jianshu.com/p/da8239581109