本文最后更新于:星期日, 五月 3日 2020, 3:21 下午
同源策略与跨域资源共享
同源策略
同源策略(Same Origin Policy, SOP) 是指在Web浏览器中,允许某个网页脚本访问另一个网页的数据,但前提是这两个网页必须有相同的URI、主机名和端口号,一旦两个网站满足上述条件,这两个网站就被认定为具有相同来源。此策略可防止某个网页上的恶意脚本通过该页面的文档对象模型访问另一网页上的敏感数据。
同源策略仅适用于脚本,这意味着一些网站可以通过相应的HTML标签来访问不同来源网站上的图片、CSS和动态加载脚本等资源,而跨站请求伪造就是利用同源策略不适用与HTML的缺陷。
同源的定义
如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的源。我们也可以把它称为“协议/主机/端口 tuple”,或简单地叫做“tuple”. (“tuple” ,“元”,是指一些事物组合在一起形成一个整体,比如(1,2)叫二元,(1,2,3)叫三元)
就URL来说,URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。
例子:
源的继承
在页面中通过 about:blank
或 javascript:
URL 执行的脚本会继承打开该 URL 的文档的源,因为这些类型的 URLs 没有包含源服务器的相关信息。
例如,about:blank
通常作为父脚本写入内容的新的空白弹出窗口的URL(例如用Window.open() 函数)。如果这个弹出窗口也包含JavaScript,则该脚本将从创建它的脚本那里继承对应的源
限制对象
- Ajax通信
- Cookie的读取
- LocalStorage的读取
- IndexDB的读取
- DOM的操作
常见的跨域方法
同源策略控制了不同源之间的交互, 例如在使用XMLHttpRequest 或 img标签时则会受到同源策略的约束。这些交互通常分为三类:
- 跨域写操作
- 跨域资源嵌入
- 跨域读操作
这三类操作中,只有跨域读操作一般是不被允许的。
跨域资源的嵌入
具备src的HTML标签都是可以跨域的。
一些例子:
- 标签嵌入跨域脚本。语法错误信息只能被同源脚本中捕捉到。
- 标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的 HTTP 头部
Content-Type
,不同浏览器有不同的限制。 - 通过
<img>
展示的图片 - 通过
<video>
和<audio>
播放的多媒体资源。 - 通过
<script>
跨域加载脚本资源 - 通过
<object>
、<applet>
和<embed>
嵌入的插件。 - 通过
@font-face
引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。 - 通过
iframe
载入的任何资源。站点可以使用 X-Frame-Options 消息头来阻止这种形式的跨域交互。
JSONP跨域
JSONP基本思想:网页中添加一个<script>
标签,向服务器请求JSON数据,服务器收到请求后,将数据放到一个指定名字的回调函数中传回来。JSONP是JSON with Padding的缩写,它只支持GET请求。
前端:
<script type="text/javascript">
function showdata(jsondata){
alert(jsondata);
}
</script>
<script src="http://192.168.45.140/data.php?callback=showdata"></script>
上面的代码向服务器发起了GET请求,请求的查询字符串中的callback参数用于指定回调函数名字。
后端:
<?php
$callback = $_GET['callback'];
$data = array("test","123","abc");
echo $callback.'('.json_encode($data).')';
document.domain
脚本可以通过document.domain的值设置为其当前域或者其当前域的父域,这意味着相同主域名下的不同子域名的页面,可以通过设置这个值来让它们的域相同,从而实现跨域。
利用条件:
- 两个域名必须属于同一个一级域名
- 两者所用的协议和端口要相同
只需要在跨域的两个页面中设置document.domain就可以了。修改document.domain的方法只适用于不同子域的框架间的交互,需要载如iframe页面。
http://www.demo.com/a.html
document.domain = 'demo.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.demo.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
};
http://script.demo.com/b.html
document.domain = "demo.com";
a.html通过创建一个iframe,去控制iframe的contentDocument,这样两个网页就可以进行交互了。
location.hash
location.hash是 URL 跟在#
后的字符串
例子:
其中location.hash就是”Examples”
将要传递的数据附在URL上,利用location.hash来进行传值。这种方法传递的数据长度是有限制的。
具体做法:
假设a.demo.com
域名下的a.html
想和b.demo.com
域名下的b.html
发生通信,a.html
首先创建一个隐藏的iframe, iframe指向b.html
,此时通过location.hash来传递参数给b.html
。但是由于同源策略限制,b.html
无法返回数据,所以要找一个中间人,位于a.demo.com
域名下的c.html
,将返回的数据传给c.html
。因为c.html
和a.html
是同源,所以可以通过c.html
来将返回的数据传回给a.html
,从而达到跨域的效果。
a.html代码:
<script>
function startRequest(){
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'http://b.demo.com/b.html#hi'; //传递的location.hash
document.body.appendChild(ifr);
}
function checkHash() {
try {
var data = location.hash ? location.hash.substring(1) : '';
if (console.log) {
console.log('Now the data is '+data);
}
} catch(e) {};
}
setInterval(checkHash, 2000);
window.onload = startRequest;
</script>
b.html代码:
window.onload = function() {
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
var data = "hello"
ifr.src = 'http://a.demo.com/c.html#' + data;
document.body.appendChild(ifr);
}
c.html代码:
parent.parent.location.hash = self.location.hash.substring(1);
window.name
基本原理: indow对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。
具体实现参考这篇文章: https://www.cnblogs.com/rainman/archive/2011/02/21/1960044.html
postMessage
这个是HTML5新增的方法,用于安全的实现跨域通信。
语法:
otherWindow.postMessage(message,targetOrigin,[ transfer])
- otherWindow: 对接收信息页面的window的引用。可以是页面中iframe的contentWindow属性;window.open的返回值;通过name或下标从window.frames取到的值。
- message: 所要发送的数据,string类型。
- targetOrigin: 用于限制otherWindow,“*”表示不作限制
a.demo.com/a.html代码:
<iframe id="ifr" src="b.demo.com/b.html"></iframe>
<script type="text/javascript">
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://b.demo.com';
ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>
b.demo.com/b.html代码
<script type="text/javascript">
window.addEventListener('message', function(event){
// 通过origin属性判断消息来源地址
if (event.origin == 'http://a.demo.com') {
alert(event.data); // 弹出"I was there!"
alert(event.source); // 对a.demo.com、a.html中window对象的引用
// 但由于同源策略,这里event.source不可以访问window对象
}
}, false);
</script>
CORS
CORS是跨域资源共享,用于让网页的受限资源能被其他域名的网页访问的一种机制。
工作原理:
跨域资源共享标准描述了,新的HTTP头部在浏览器有权限的时候,应该以如何的形式发送请求到远程URLs。虽然服务器会有一些校验和认证,但是浏览器有责任去支持这些头部以及增加相关的限制。
对于能够修改数据的Ajax和HTTP请求方法(特别是 GET
以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST
请求),浏览器必须首先使用 OPTIONS
方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)
具体介绍参考: http://www.ruanyifeng.com/blog/2016/04/cors.html
REFERENCE
- https://lightless.me/archives/review-SOP.html
- https://www.anquanke.com/post/id/86078
- https://xz.aliyun.com/t/224#toc-2
- https://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html
- https://segmentfault.com/a/1190000000702539#item-5
- https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
- https://zh.wikipedia.org/wiki/%E5%90%8C%E6%BA%90%E7%AD%96%E7%95%A5
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!