参考
https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
一. 同源策略
同源策略(same-origin policy):限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。
目的:防止CSRF(Cross-Site Request Forgery,跨站请求伪造)。
同一个源:协议,域名,端口都相同。 IE例外,IE中只要协议和域名相同,就认为是同一个源。
1. 同源策略限制的内容
- 跨源网络访问:AJAX请求。
- 跨源 DOM 访问:DOM。
- 跨源脚本API访问:Javascript的APIs,如 iframe.contentWindow, window.parent, window.open 和 window.opener 。
- 跨源数据存储访问:Cookie、LocalStorage 和 IndexDB。
2. 源的更改
页面可以改变其来源(仅限由当前域改为父域)。
- 对于aa.xx.com,可以在当前来源的脚本中用
document.domain="xx.com"
改变来源为父域xx.com
。 - 对于xx.com,不能改为yy.com。
二. 跨域
不同源之间交互时,会受同源策略的约束。相应的,也有一些措施绕过约束。
1. 交互方式
名称 | 是否允许跨域 | 举例 |
---|---|---|
跨域写操作 Cross-origin writes | 允许 | 链接(links),重定向以及表单提交。特定少数的HTTP请求需要添加 preflight。 |
跨域资源嵌入 Cross-origin embedding | 允许 | <script src="..."></script> 标签嵌入跨域脚本<link rel="stylesheet" href="..."> 标签嵌入CSS(CSS的跨域需要一个设置正确的Content-Type消息头)<img> 嵌入图片。<video> 和 <audio> 嵌入多媒体资源<object> , <embed> 和 <applet> 的插件。@font-face 引入的字体<frame> 和 <iframe> 载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。 |
跨域读操作 Cross-origin reads | 不允许 | 常可以通过内嵌资源来巧妙的进行读取访问。例如可以读取嵌入图片的高度和宽度,调用内嵌脚本的方法,或availability of an embedded resource. |
2. 跨域方法(读操作)
1. AJAX跨域。
(1) JSONP
JSONP特点:简单,老式浏览器全部支持。但是只支持GET请求,不支持POST请求,,服务器需要改造。
原理:
1. 网页动态添加<script>
元素,通过元素的src
参数向服务器发请求(src
参数中需要指定回调函数的函数名),这种做法不受同源政策限制。回调函数需要提前定义好。
2. 服务器收到请求后,将数据放在指定的回调函数里传回来。
3. 客户端接收到返回的脚本后,立刻执行,即调用回调函数。
(2) CORS
CORS:跨域资源共享(Cross-Origin Resource Sharing)。
CORS特点:需要浏览器和服务器同时支持。CORS支持所有类型的HTTP请求,但不兼容IE10以下浏览器。
原理:
1. 页面发起AJAX请求。浏览器发现是跨域请求时,会自动在请求头中添加Origin:http://xx.com
字段,说明本次请求来自哪个源(协议 + 域名 + 端口)。
2. 服务器解析请求,如果不支持当前页面的跨域请求,则返回头信息不包含Access-Control-Allow-Origin字段的数据。否则在响应头中声明允许的页面header('Access-Control-Allow-Origin:*');
。
3. 浏览器根据响应头是否包含Access-Control-Allow-Origin字段,判断跨域是否成功。
(3) 服务器代理
浏览器先请求同源服务器,再由同源服务器请求跨域资源。
2. 数据存储跨域
(1) Cookie 跨域
一个页面可以为本域和任何父域设置Cookie,浏览器允许任何子域来访问父域的cookie。比如aa.xx.com可以为aa.xx.com和xx.com设置Cookie,nn.aa.xx.com可以为nn.aa.xx.com、aa.xx.com和xx.com设置Cookie。
可以在页面设置,或在服务器设置:
- 页面设置:
document.domain = 'xx.com';
- 服务器设置:在设置Cookie的时指定Cookie的所属域名为父域名,比如xx.com。此时xx.com下的所有域名共用同一个Cookie。
Set-Cookie: key=value; domain=xx.com; path=/
(2) LocalStorage 和 IndexDB 跨域
需要借助PostMessage API。
3. 脚本API跨域
Javascript的APIs中,如 iframe.contentWindow, window.parent, window.open 和 window.opener 允许文档间直接相互引用。当两个文档的源不同时,这些引用方式将对 Window 和 Location对象的访问添加限制。可以使用window.postMessage 作为替代方案,提供跨域文档间的通讯。
(1) window.postMessage()
postMessage方法可以安全地实现跨源通信(需要2个窗口配合)。用于不同页面(iframe、window.open返回的窗口对象)的脚本通信。第二个参数指定哪些窗口收到数据,可以用通配符*。
- 消息发送
var win = document.getElementsByTagName('iframe')[0].contentWindow; //iframe窗口
//var win = window.open('openedWinURL', 'openedWinName'); //其他页签打开的窗口
var obj = { name: 'Jack' };
win.postMessage(JSON.stringify({key: 'get', data: obj}), 'http://xx.com');
- 消息接收
window.onmessage = function(e) {
if (e.origin != 'http://xx.com') return;
var d = JSON.parse(e.data);
localStorage.setItem(d.key, JSON.stringify(d.data));
};
(2) window.name
同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。可以跨域访问。
4. 跨域操作DOM(iframe)
改document.domain
为同一个域即可。
3. 阻止跨域访问(防止CSRF)
- 关键操作不用 GET。
- POST 请求增加额外验证(验证码,随机数,HTTP Referer(程序或服务器配置设置禁止跨域访问),Token)等。