目录
- 理解同源策略
-
- 禁止两个互不相关的站点互相访问cookie
- 禁止A域的网站访问B域的资源
- 禁止与网站与iframe之间的Dom查询
- 理解跨域
-
- 在保证安全的基础上,让A域网站可以访问B域资源
- 在保证安全基础上,让A域的网站可以访问B域的cookie
- 跨域实践
-
- 标签跨域
-
- 优势
- 劣势
- 实践
- JSONP跨域
-
- 优势
- 劣势
- 实现原理
-
- 前端
- 后端
- iframe跨域
-
- 优势
- 劣势
- 实现原理
- CROS跨域
- 代理跨域
之前研究过跨域,但都浮在表层,感觉很是别扭。所以就想着扒一扒各种跨域原理,让自己的理解更加深刻。
理解同源策略
这几天研究下来,我自己琢磨出一个对跨域的理解:跨域是对同源策略的一个补充,让同源策略更加的灵活。因此我想优先聊一聊同源策略的概念。
为了安全,浏览器都会支持同源策略,主要有如下几个作用:
禁止两个互不相关的站点互相访问cookie
如果没有限制将会特别可怕,参考如下场景:
- A想在网上银行进行转账操作,登录成功后服务器会在浏览器下域名为Bank.com的cookie中存放一个token。
- 以后每次访问该网上银行,都会将token传给服务器,服务器验证通过,就代表登录成功。
- 此时B给A发送了一个不可描述的网站xxx.com,A激动的快速打开该网站
- 但不曾想,xxx网站竟然在暗地里调用该银行的转账接口,向某境外账户转了1块钱。
正常转账接口都会做token验证,但在刚才的栗子中,由于没有同源策略的限制,xxx.com网站可以轻松获取Bank.com下的token,从而能够成功转账。
禁止A域的网站访问B域的资源
其实上面的栗子有个前提,那就是在xxx网站可以访问Bank的转账接口,而这个前提也是被同源策略进行管理的。
禁止与网站与iframe之间的Dom查询
如果这个被开放了,那就厉害坏了,参考如下场景:
- 早上醒来发现有一封邮件,提示你信用卡异常消费,让你点击邮件里提供的网站
(真实官网banik.com
)网站查询bank.com
- 你按照他的引导进入
,登录查看后发现原来是虚惊一场,并没有什么异常消费,接着高高兴兴的上班去了banik.com
- 到了晚上,有短信提示你进行一笔大额转账,抓紧登录
查看,发现钱真的被转走。???bank.com
banik.com
是诈骗邮件提供的一个钓鱼网站,这个网站比较简单,共有两部分:
- 一个全屏iframe标签并加载真实官网
bank.com
- 一个获取iframe里账号密码输入框节点的脚本
在没有该同源策略的限制下,
banik.com
可以访问到
bank.com
里的dom节点,接着获取你输入的账号密码岂不轻而易举。
理解跨域
之前提到过我对跨域的理解:跨域是对同源策略的一个补充,让同源策略更加的灵活。现在我们就聊聊都跨域对同源策略都做了哪些补充。(有其他想法的欢迎留言补充)
在保证安全的基础上,让A域网站可以访问B域资源
下面通过问答的方式对这一点进行分解。
- 为什么会出现A域网站访问B域资源的情况
- 对于小站点,把网站的js、css、图片等资源放在同一个服务器是没有问题的,但当网站的体量越来越大,用户量越来越多时,对服务器的压力会倍增,从而影响服务器的响应效率。因此我们需要将资源放在不同的服务器,从而产生了跨域的需求。
- 随着前端的繁荣发展,演变出很多优秀的第三方服务,而这些第三方服务肯定跟公司的域名不一致,因此产生也产生了跨域需求
- 有哪些官方跨域方式
- 在早期,官方就意识到上面说的第一个问题,因此让
、script
标签都支持跨域img
- 后来针对第二个问题,专门推出一个W3C标准:CORS(Cross-origin resource sharing),后面详解
- 在早期,官方就意识到上面说的第一个问题,因此让
- 有哪些非官方跨域
指的就是利用一些奇淫技巧实现跨域,主要有如下三种
-
JSONP - 基于script标签
仅支持GET请求,可以获取服务器的响应数据
-
img - 基于img标签
仅支持GET请求,无法获取服务器的响应数据
-
iframe - 基于iframe标签
支持各种请求方式,无法获取服务器的响应数据
-
在保证安全基础上,让A域的网站可以访问B域的cookie
在同源策略中,是明确禁止这种行为的,但上有政策,下有对策,可以采用曲线救国的方式来实现,各种广告联盟就是这种做的,参考如下场景:
- 我有一个日访问量达到百万的网站
,想接入JD的广告联盟赚点外快,各种流程走完以后我拿到如下代码,并且将该代码嵌入到我网站的广告模块(你也可以直接在浏览器中访问下面iframe指定的链接,效果是一样的)juzi.com
- 发版以后,想看看效果。于是我去京东官网搜索了两个关键词
和婴儿车
,接着刷新我的网站,发现广告模块开始给我推送iphone6
和婴儿车
的商品。iphone6
在该栗子中,最为神奇的点在于,一个跟京东完全不相关的网站怎么知道你在京东访问过什么。接下来开始揭秘:
- 首先要申明一点,除了京东提供给我的广告代码,在
网站中没有任何直接或间接的与京东有任何交互操作。juzi.com
- 关键在于京东给我的那些代码,一个iframe标签并指定其src指向京东的二级域名,由于iframe支持子域名与根域名互相访问cookie,因此在橘子里的iframe向京东发起请求时,会带上你刚才在访问京东官网时留下的cookie,所以我
中的京东联盟广告可以知道你想要的内容。juz.com
- 至于iframe是干嘛的,就不再这里啰嗦了。
跨域实践
在本文中我只提第一种场景:在保证安全的基础上,让A域网站可以访问B域资源,至于第二种,通过广告联盟的栗子便可知道怎么做我就不做过多描述。
接下来我们就按照跨域方式出现先后顺序挨个实践
标签跨域
为了缓解服务器压力,w3c标准让
script
和
img
标签支持跨域操作,因此我们可以将将接口请求直接放到这类标签中,另外为了操作更加灵活,一般使用动态方式创建这两种标签
优势
不需要后端配合
劣势
- 仅仅支持GET请求
- 不能够获取响应的数据
实践
function addTrack(params) {
const trackTag = document.createElement('script');
// 使用qs库序列化对象
trackTag.src = `wwww.addTrack.com?${Qs.stringify(params)}`;
// 如果使用的是img标签,建议加上这句,避免影响用户体验
trackTag.display = 'none';
document.body.appendChild(trackTag);
}
addTrack({ page: 'home' });
JSONP跨域
全称是JSON with Padding,它还是基于
script
标签
优势
可以获取响应的数据
劣势
- 仅支持get
- 需要后端配合
实现原理
前端
function getRequest(data) {
const script = document.createElement('script')
// 1. 主要是多了一个函数来获取请求的响应
window.jsonpCb = (res) => {
document.body.removeChild(script)
delete window.jsonpCb
}
// 2. 通过cb字段告知服务器前端用于接收响应的函数是jsonpCb
script.src = `www.juzi.com?${Qs.stringify(data)}&cb=jsonpCb`
document.body.appendChild(script)
}
后端
router.get('/get', function(req, res, next) {
// 1. 获取前端传过来的函数
var _callback = req.query.cb;
var responseData = { email: '[email protected]', name: 'GG' };
// 2. 判断是否有cb字段,如果没有cb返回相应提示
if (_callback){
// 3. 设置返回数据的数据类型,这里要指定为js代码
res.type('text/javascript');
// 4. 拼出如下字符串:jsonpCb({ email: '[email protected]', name: 'GG' }),做响应
res.send(_callback + '(' + JSON.stringify(responseData) + ')');
} else {
res.send('没有获取到回调函数');
}
});
在刚开始接触JSONP时,看上面的代码简直是一头雾水。要理解上面代码,先要了解一下
script
标签是怎么工作的,比如我要引入jQuery库,我们只需要这样写,便可以使用jQuery的函数
但浏览器真实操作是将
www.jQuery.com
响应的内容都放到
script
标签中,也就是说上面代码会变成如下代码
<script type='text/javascript'>
// jQuery源码
</script>
接下来我们在按照上面的栗子,我们来分析一下JSONP是怎样的流程,在前端做的事情如下:
浏览器的真实操作如下:
<script type='text/javascript'>
// 下面这段代码是接口响应的内容,由此应该了解前端设置的回调就是在这里执行的
jsonpCb({ email: '[email protected]', name: 'GG' })
</script>
iframe跨域
iframe跨域有如下两种,本小节说的是第二种
- 像广告联盟那样跨域获取cookie,
- 发送跨域请求
优势
此方式是搭配form组件才能使用,因此只要from支持的请求方式,在这里都支持
劣势
因为同源策略禁止跨窗口之间的DOM查询,正常情况下此种方式仅仅可以知道请求是否成功,但无法获取请求数据,但网友的智慧的无穷的,想出如下三种奇淫技巧来实现跨域窗口之前的通信,分别是:
片段识别符
/
window.name
/
跨文档通信API
。在此就不做过多说明,感兴趣的朋友可以试试。
实现原理
底层原理跟广告联盟一样,但方式上有些差异。
const requestPost = ({url, data}) => {
const iframe = document.createElement('iframe')
iframe.name = 'iframePost'
iframe.style.display = 'none'
document.body.appendChild(iframe)
const form = document.createElement('form')
const node = document.createElement('input')
// 因为当借口访问成功,iframe才算加载完毕,因此可以通过这个机制来判定接口是否访问成功
iframe.addEventListener('load', function () {
console.log('post success')
})
form.action = url
form.target = iframe.name
form.method = 'post'
for (let name in data) {
node.name = name
node.value = data[name].toString()
form.appendChild(node.cloneNode())
}
// 表单元素需要添加到主文档中.
form.style.display = 'none'
document.body.appendChild(form)
form.submit()
// 表单提交后,就可以删除这个表单,不影响下次的数据发送.
document.body.removeChild(form)
}
// 使用方式
requestPost({
url: 'http://localhost:9871/api/iframePost',
data: {
msg: 'helloIframePost'
}
})
上诉代码看懂需要两个前提条件:
- 理解了广告联盟的运作机制
- 理解iframe和form标签,尤其要知道form标签的target属性是干嘛的
CROS跨域
CROS全称Cross-origin resource sharing,想要做详细了解的,请移步查看阮一峰的跨域资源共享 CORS。下面我通过问答的方式记录一下我学习cros过程中碰到的疑问。
- cros是谁的功能,网络库?
CORS是一个W3C标准,是每个浏览器要遵守的规范
- 预检请求是谁发出的?
客户端发送一个请求,浏览器会做拦截,判断是否是跨域请求,如果时就主动向服务器发出一个opitons请求
- 是谁在响应预检请求?
是我们自己的服务器
- 预检请求的Origin字段是浏览器自动带上的,不用自己特意写
- 在后端没有写options请求相关接口的情况下,是怎么将数据响应给客户端的?
查了很多资料都没有查到,目前理解如为:只要服务器支持options请求,就自动将如下的响应头返回,不需要专门做干预
- Access-Control-Allow-Origin
- Access-Control-Allow-Headers
- Access-Control-Allow-Methods
- 后端需要做什么事情
- 支持options请求
- 在支持options请求的地方,根据自己情况设置如下几个字段:
- Access-Control-Allow-Origin
- Access-Control-Allow-Headers
- Access-Control-Allow-Methods
- Access-Control-Allow-Credentials
- 预检请求的响应流程
代理跨域
虽然浏览器有跨域限制,但服务器没有啊。因此可以通过自己搭建服务器A,让前端访问A服务器,服务器A再访问想要访问的接口来实现跨域。