同源限制理解与解决
同源策略是对JavaScript代码能够操作哪些web内容的一条完整的安全限制。
什么是同源
URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。
下表给出了相对http://store.company.com/dir/page.html同源检测的示例:
| URL | 结果 | 原因 |
|---|---|---|
| http://store.company.com/dir2/other.html | 成功 | 只有路径不同 |
| http://store.company.com/dir/inner/another.html | 成功 | 只有路径不同 |
| https://store.company.com/secure.html | 失败 | 不同协议 ( https和http ) |
| http://store.company.com:81/dir/etc.html | 失败 | 不同端口 ( http:// 80是默认的) |
| http://news.company.com/dir/other.html | 失败 | 不同域名 ( news和store ) |
同源策略阻止什么
同源策略最初由网景公司添加到浏览器,当前所有的浏览器都支持此功能。
初始的功能是为了限制非同域网页之间cookie的访问,例如a.html页面无法访问非同域的b.html页面的cookie。
功能是为了保护网站信息安全,比如,一个电脑可以访问很多网站,总不能此网站可以读取另一个网站的信息,尤其是涉及到重要信息,比如用户名和密码。
随着浏览器功能的增强,同源策略由最初限制cookie的访问,发展到限制多种本地信息:
Cookie、LocalStorage 和 IndexedDB访问限制。
a.html的脚本无法访问b.html页面下的上述种类浏览器缓存,为了防止恶意网站通过js获取用户其他网站的cookie。DOM获取限制。
在浏览器中,<script>、<img>、<iframe>、<link>等含有src属性的标签都可以加载跨域资源,而不受同源限制,但浏览器限制了JavaScript的权限使其不能读、写加载的内容。
如果没有这一条,恶意网站可以通过iframe打开银行页面,可以获取dom就相当于可以获取整个银行页面的信息。限制 ajax 请求,准确来说是限制操作 ajax 响应结果,本质上跟上一条是一样的
假设有一个黑客叫做小黑,他从网上抓取了一堆美女图做了一个网站,每日访问量爆表。
为了维护网站运行,小黑挂了一张收款码,觉得网站不错的可以适当资助一点,可是无奈伸手党太多,小黑的网站入不敷出。
于是他非常生气的在网页中写了一段js代码,使用ajax向淘宝发起登陆请求,因为很多数人都访问过淘宝,所以电脑中存有淘宝的cookie,不需要输入账号密码直接就自动登录了,然后小黑在ajax回调函数中解析了淘宝返回的数据,得到了很多人的隐私信息,转手一卖,小黑的网站终于盈利了。
如果跨域也可以发送AJAX请求的话,小黑就真的获取到了用户的隐私并成功获利了!!!
同源策略允许什么
- 同源策略只对网页的HTML文档做了限制,对加载的其他静态资源如javascript、css、图片等仍然认为属于同源。
- 页面中的
<script>、<img>、<ifram>、<link>等标签、重定向以及表单提交是不会受到同源策略限制的,比如在网站www.foo.com下提交一个表单到www.bar.com是完全可以的。 - 跨域资源嵌入是允许的。
脚本本身的来源和同源策略并不相关,相关的是脚本所嵌入的文档的来源,理解这一点很重要。例如、假设一个来自主机A的脚本被包含到(使用<script>标记的src属性)宿主B的一个web页面中。这个脚本的来源就是主机B而不是A,并且可以完整的访问包含它的文档内容。如果脚本打开一个新的窗口载入来自主机B的另一个文档,脚本对这个文档的内容也完全具有访问权限。但是,如果脚本打开第三个窗口并载入一个来自主机C吃文档(或者是主机A),同源策略会发挥作用,阻止脚本访问这个文档。
如何跨域
1.iframe跨域 参考happy哥
document.domain跨域此方案仅限主域相同,子域不同的跨域应用场景。
同源策略会给那些使用多个子域的大站点带来一些问题。如,来自
home.example.com的文档里脚本想要合法的读取从developer.example.com上的文档的属性。为了支持这种类型的多域名站点,可以使用Document对象的domain属性。在默认情况下,属性domain存放的是载入文档的服务器的主机名。可以设置这一个属性,不过字符串必须具有有效的域前缀或他本身。因此,如果一个domain属性的初始值是字符串home.example.com,就可以把它设置为example.com。但是不能设置为home.example或者ample.com。另外domain值中必须有一个.,不能把它设置为com或其他顶级域名。如果两个窗口(或窗体)包含的脚本把domain设置成了相同的值,那么这两个窗口就不会再受同源策略的约束。
在home.example.com和developer.example.com的文档同时使用脚本设置1
document.domain = 'example.com'实现跨域,cookie也可以用这种方法实现跨域
location.hash跨域
当主域不同时,可以使用此方法
在url中,http://www.baidu.com#helloword的#helloworad就是location.hash,改变hash值不会导致页面刷新,所以可以利用hash值来进行数据的传递,当然数据量是有限的。
父窗口可以把信息,写入子窗口的location.hash。1
2var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;子窗口通过监听
hashchange事件得到通知。1
2
3
4
5window.onhashchange = checkMessage;
function checkMessage() {
var message = window.location.hash;
// ...
}同样的,子窗口也可以改变父窗口的片段标识符。
1
parent.location.href= target + "#" + hash;window.name跨域window.name(一般在js代码里出现)的值不是一个普通的全局变量,而是当前窗口的名字,要注意的是每个iframe都有包裹它的window,而这个window是top window的子窗口,而它自然也有window.name的属性,window.name属性的神奇之处在于name值在不同的页面(甚至不同域名)加载后依旧存在(如果没有修改则值不会变化),并且可以支持非常长的name值(2MB)
举个简单的例子:你在某个页面的控制台输入:1
2window.name = "hello world"
window.location = "http://www.baidu.com"页面跳转到了百度首页

但是window.name却被保存下来了,还是hello world。这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。
跨文本消息
postMessagepostMessage是 HTML5 新增加的一项功能,跨文档消息传输(Cross Document Messaging),目前:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 都支持这项功能。
举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。1
2var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');子窗口向父窗口发送消息的写法类似。
1
window.opener.postMessage('Nice to see you', 'http://aaa.com');父窗口和子窗口都可以通过message事件,监听对方的消息。
1
2
3window.addEventListener('message', function(e) {
console.log(e.data);
},false);message事件的事件对象event,提供以下三个属性。
1
2
3event.source:发送消息的窗口
event.origin: 消息发向的网址
event.data: 消息内容
- Ajax跨域
Jsonp
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。它的基本思想是,网页通过添加一个
<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。首先,网页动态插入
<script>元素,由它向跨源网址发出请求。1
2
3
4
5
6
7
8
9
10
11
12
13function addScriptTag(src) {//定义添加script标签函数
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {//当文档加载完成时,发送Jsonp请求
addScriptTag('http://example.com/ip?code=123&callback=foo');
//url中包含code需要的数据,与需要的回调函数callback
}
function foo(data) {//回调函数,当数据返回时,调用
console.log('Your public IP address is: ' + data.ip);
};上面代码通过动态添加
<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。1
2
3foo({
"ip": "8.8.8.8"
});由于
<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。CORS跨域资源共享
CORS的主要工作在后端,是HTML5规范定义的如何跨域访问资源。
Origin表示本域,也就是浏览器当前页面的域。当JavaScript向外域(如sina.com)发起请求后,浏览器收到响应后,首先检查Access-Control-Allow-Origin是否包含本域,如果是,则此次跨域请求成功,如果不是,则请求失败,JavaScript将无法获取到响应的任何数据。
用一个图来表示就是:
假设本域是my.com,外域是sina.com,只要响应头Access-Control-Allow-Origin为http://my.com,或者是*,本次请求就可以成功。可见,跨域能否成功,取决于对方服务器是否愿意给你设置一个正确的
Access-Control-Allow-Origin,决定权始终在对方手中。上面这种跨域请求,称之为“简单请求”。简单请求包括GET、HEAD和POST(POST的Content-Type类型 仅限
application/x-www-form-urlencoded、multipart/form-data和text/plain),并且不能出现任何自定义头(例如,X-Custom: 12345),通常能满足90%的需求。无论你是否需要用JavaScript通过CORS跨域请求资源,你都要了解CORS的原理。最新的浏览器全面支持HTML5。在引用外域资源时,除了JavaScript和CSS外,都要验证CORS。例如,当你引用了某个第三方CDN上的字体文件时:
1
2
3
4
5/* CSS */
@font-face {
font-family: 'FontAwesome';
src: url('http://cdn.com/fonts/fontawesome.ttf') format('truetype');
}如果该CDN服务商未正确设置
Access-Control-Allow-Origin,那么浏览器无法加载字体资源。对于
PUT、DELETE以及其他类型如application/json的POST请求,在发送AJAX请求之前,浏览器会先发送一个OPTIONS请求(称为preflighted请求)到这个URL上,询问目标服务器是否接受:1
2
3
4OPTIONS /path/to/resource HTTP/1.1
Host: bar.com
Origin: http://my.com
Access-Control-Request-Method: POST服务器必须响应并明确指出允许的Method:
1
2
3
4HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
Access-Control-Max-Age: 86400浏览器确认服务器响应的
Access-Control-Allow-Methods头确实包含将要发送的AJAX请求的Method,才会继续发送AJAX,否则,抛出一个错误。由于以POST、PUT方式传送JSON格式的数据在REST中很常见,所以要跨域正确处理POST和PUT请求,服务器端必须正确响应OPTIONS请求。
- cookie跨域
document.domain跨域,与iframe类似
Jsonp跨域
Ajax+cors跨域
默认情况下,标准的跨域请求是不会发送不同源的cookie的1
2var xhr = new XMLHttpRequest();
xhr.withCredentials = true;为了安全,标准里不允许
Access-Control-Allow-Origin: *,*必须指定明确的、与请求网页一致的域名。
- LocalStorage跨域
- postMessage
参考:
https://blog.csdn.net/hansexploration/article/details/80314948
http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
https://www.liaoxuefeng.com/wiki/1022910821149312/1023022332902400
https://blog.csdn.net/shuidinaozhongyan/article/details/78155310
https://blog.csdn.net/chou_out_man/article/details/80664413
https://blog.csdn.net/itcats_cn/article/details/82318092
https://www.cnblogs.com/happy-8090/p/11570998.html