浏览器跨域是一个前端很常见的问题。
造成跨域的两种策略浏览器的同源策略会导致跨域,这里同源策略又分为以下两种DOM同源策略:
1.禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
//例如
let iframeSon = document.getElementById("myIframe").contentWindow.document.getElementById("son");
console.log(iframeSon)
如果是iframe和本页面为不同的源页面的情况下
我们就会出现上图的报错
2.XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作。
AJAX同源策略主要用来防止CSRF攻击。如果没有AJAX同源策略,相当危险,我们发起的每一次HTTP请求都会带上请求地址对应的cookie,那么可以做如下攻击:
- 用户登录了自己的银行页面 http://mybank.com,http://mybank.com向用户的cookie中添加用户标识。
- 用户浏览了恶意页面 http://evil.com。执行了页面中的恶意AJAX请求代码。
- http://evil.com向http://mybank.com发起AJAX HTTP请求,请求会默认把http://mybank.com对应cookie也同时发送过去。
- 银行页面从发送的cookie中提取用户标识,验证用户无误,response中返回请求数据。此时数据就泄露了。
- 而且由于Ajax在后台执行,用户无法感知这一过程。
DOM同源策略也一样,如果iframe之间可以跨域访问,可以这样攻击:
- 做一个假网站,里面用iframe嵌套一个银行网站 http://mybank.com。
- 把iframe宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
- 这时如果用户输入账号密码,我们的主网站可以跨域访问到http://mybank.com的dom节点,就可以拿到用户的输入了,那么就完成了一次攻击。
(如果账号密码你感觉不会让你损失钱,你可以试着输入一下信用卡卡号+安全码,然后你就能感觉到花钱的快乐)
所以说有了跨域跨域限制之后,我们才能更安全的上网了。
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出[XMLHttpRequest
]请求,从而克服了AJAX只能[同源]使用的限制。
下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
对于客户端,我们还是正常使用xhr对象发送ajax请求。
唯一需要注意的是,我们需要设置我们的xhr属性withCredentials为true,不然的话,cookie是带不过去的,
设置:
xhr.withCredentials = true;
对于服务器端,需要在 response header中设置如下两个字段:
Access-Control-Allow-Origin: [http://www.yourhost.com](https://link.zhihu.com/?target=http%3A//www.yourhost.com)
Access-Control-Allow-Credentials:true
这样,我们就可以跨域请求接口了。
jsonp实现跨域
基本原理就是通过动态创建script标签,然后利用src属性进行跨域。
demo.js
// 定义一个fun函数
function fun(fata) {
console.log(data);
};
// 创建一个脚本,并且告诉后端回调函数名叫fun
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.type = 'text/javasctipt';
script.src = 'demo.js?callback=fun';
body.appendChild(script);
这样就能获取到数据的同时直接执行demo.js中的fun函数
postMessage实现跨域
ES5新增的 postMessage() 方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递.
语法: postMessage(data,origin)
data: 要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候建议使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果.
origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为”*”,这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/“。
父页面发送消息:
window.frames[0].postMessage('message', origin)
iframe接受消息:
window.addEventListener('message',function(e){
if(e.source!=window.parent) return;//若消息源不是父页面则退出
//TODO ...
});
其中 e 对象有三个重要的属性
1.data, 表示父页面传递过来的message
2.source, 表示发送消息的窗口对象
3.origin, 表示发送消息窗口的源(协议+主机+端口号)
document.domain来进行跨域
通过修改document的domain属性,我们可以在域和子域或者不同的子域之间通信(即它们必须在同一个一级域名下). 同域策略认为域和子域隶属于不同的域,比如a.com和 script.a.com是不同的域,这时,我们无法在a.com下的页面中调用script.a.com中定义的JavaScript方法。但是当我们把它们document的domain属性都修改为a.com,浏览器就会认为它们处于同一个域下,那么我们就可以互相获取对方数据或者操作对方DOM了。
比如, 我们在 www.a.com/a.html 下, 现在想获取 www.script.a.com/b.html, 即主域名相同, 二级域名不同. 那么可以这么做:
document.domain = 'a.com';
var iframe = document.createElement('iframe');
iframe.src = 'http://www.script.a.com/b.html';
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.addEventListener('load',function(){
//TODO 载入完成时做的事情
//var _document = iframe.contentWindow.document;
//...
},false);
注意:
2个页面都要设置, 哪怕 a.html 页已处于 a.com 域名下, 也必须显式设置.
document.domain只能设置为一级域名,比如这里a页不能设置为www.a.com (二级域名).
利用domain属性跨域具有以下局限性:
两个页面要在同一个一级域名下, 且必须同协议, 同端口, 即子域互跨;
只适用于iframe(几乎没luan用 ̄へ ̄)
Internet Explorer同源策略绕过
Internet Explorer8以及前面的版本很容易通过document.domain实现同源策略绕过, 通过重写文档对象, 域属性这个问题可以十分轻松的被利用.
var document;
document = {};
document.domain = 'http://www.a.com';
console.log(document.domain);
如果你在最新的浏览器中运行这段代码, 可能在JavaScript控制台会显示一个同源策略绕过错误(真正没luan用(艹皿艹))
底下的文章中还有几个不常用的方法,都几乎是在特定的情况下才能生效,就不一一解释了。
那么我们插件中又是怎么解决跨域的呢?
在插件中的background是不受域的限制的,因此可以轻松发送ajax请求给其他网站。
background的权限非常高,几乎可以调用所有的Chrome扩展API(除了devtools),而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置CORS。
经过测试,其实不止是background,所有的直接通过chrome-extension://id/xx.html这种方式打开的网页都可以无限制跨域。
然后说说上周发现插件的一个坑
最近要做一个页面可以内嵌许多其他的外部网页,于是使用了iframe
然后使用iframe的时候少许网页会出现这种情况
那么这个X-Frame-Options又是个啥呢?
X-Frame-Options是什么?
X-Frame-Options是一个HTTP标头(header),用来告诉浏览器这个网页是否可以放在iFrame内。例如:
X-Frame-Options: DENY X-Frame-Options: SAMEORIGIN X-Frame-Options: ALLOW-FROM http://caibaojian.com/
第一个例子告诉浏览器不要(DENY)把这个网页放在iFrame内,通常的目的就是要帮助用户对抗点击劫持。
第二个例子告诉浏览器只有当架设iFrame的网站与发出X-Frame-Options的网站相同,才能显示发出X-Frame-Options网页的内容。
第三个例子告诉浏览器这个网页只能放在http://caibaojian.com//网页架设的iFrame内。
不指定X-Frame-Options的网页等同表示它可以放在任何iFrame内。
X-Frame-Options可以保障你的网页不会被放在恶意网站设定的iFrame内,令用户成为点击劫持的受害人。
简单的来说,这东西就是用来阻止自己的网页被嵌套在iframe中然后被网站盗取资料的
但是如果你装了chrome插件的话在background中进行一个这样的设置,就可以绕过网站的设置
chrome.webRequest
使用 chrome.webRequest API 监控与分析流量,还可以实时地拦截、阻止或修改请求。
onBeforeRequest(可以为同步)
当请求即将发出时产生。这一事件在 TCP 连接建立前发送,可以用来取消或重定向请求。
onBeforeSendHeaders(可以为同步)
当请求即将发出并且初始标头已经准备好时产生。这一事件是为了使扩展程序能够添加、修改和删除请求标头(*)。onBeforeSendHeaders 事件将传递给所有订阅者,所以不同的订阅者都可以尝试修改请求。有关具体如何处理的细节,请参见实现细节部分。这一事件可以用来取消请求。
onSendHeaders
当所有扩展程序已经修改完请求标头并且展现最终版本时产生。这一事件在标头发送至网络前触发,仅用于提供信息,并且以异步方式处理,不允许修改或取消请求。
onHeadersReceived(可以为同步)
每当接收到 HTTP(S) 响应标头时产生。由于重定向以及认证请求,对于每次请求这一事件可以多次产生。这一事件是为了使扩展程序能够添加、修改和删除响应标头,例如传入的 Set-Cookie 标头。缓存指示是在该事件触发前处理的,所以修改 Cache-Control 之类的标头不会影响浏览器的缓存。它还允许您重定向请求。
onAuthRequired(可以为同步)
当请求需要用户认证时产生。这一事件可以同步处理,提供认证凭据。注意,扩展程序提供的凭据可能无效,注意不要重复提供无效凭据,陷入无限循环。
onBeforeRedirect
当重定向即将执行时产生,重定向可以由 HTTP 响应代码或扩展程序触发。这一事件仅用于提供信息,并以异步方式处理,不允许修改或取消请求。
onResponseStarted
当接收到响应正文的第一个字节时产生。对于 HTTP 请求,这意味着状态行和响应标头已经可用。这一事件仅用于提供信息,并以异步方式处理,不允许修改或取消请求。
onCompleted
当请求成功处理后产生。
onErrorOccurred
当请求不能成功处理时产生。
网络请求 API 保证对于每一个请求,onCompleted 或 onErrorOccurred 是最终产生的事件,除了如下例外:如果请求重定向至 data:// URL,######onBeforeRedirect 将是最后报告的事件。
这里我们主要使用的是onHeadersReceived
chrome.webRequest.onHeadersReceived.addListener(
(details)=> {
for (var i = 0; i < details.responseHeaders.length; ++i) {
if (details.responseHeaders[i].name.toLowerCase() == 'x-frame-options') {
details.responseHeaders.splice(i, 1);
return {
responseHeaders: details.responseHeaders
};
}
}
}, {
urls: ["*://zhihu.com/*"]
}, ["blocking", "responseHeaders"]);
这里我们主要的就是用onSendHeaders,大家可以看到代码中,每当接收到 HTTP(S) 响应标头时,将会获取到其中的responseHeaders,然后将其中的‘x-frame-options’这个设置给砍掉。这样浏览器的最终结果就成功绕过了iframe的设置,强行的将网页变成内嵌的iframe。
至于浏览器的安全问题。。。。。
。。
你都装了这么流氓的插件了,我们网站能咋办,自己背锅。
文章资料来源
http://www.ruanyifeng.com/blog/2016/04/cors.html
http://louiszhai.github.io/2016/01/11/cross-domain/
https://crxdoc-zh.appspot.com/extensions/webRequest#implementation