天天看点

扒一扒各种跨域的原理理解同源策略理解跨域跨域实践

目录

  • 理解同源策略
    • 禁止两个互不相关的站点互相访问cookie
    • 禁止A域的网站访问B域的资源
    • 禁止与网站与iframe之间的Dom查询
  • 理解跨域
    • 在保证安全的基础上,让A域网站可以访问B域资源
    • 在保证安全基础上,让A域的网站可以访问B域的cookie
  • 跨域实践
    • 标签跨域
      • 优势
      • 劣势
      • 实践
    • JSONP跨域
      • 优势
      • 劣势
      • 实现原理
        • 前端
        • 后端
    • iframe跨域
      • 优势
      • 劣势
      • 实现原理
    • CROS跨域
    • 代理跨域
之前研究过跨域,但都浮在表层,感觉很是别扭。所以就想着扒一扒各种跨域原理,让自己的理解更加深刻。

理解同源策略

这几天研究下来,我自己琢磨出一个对跨域的理解:跨域是对同源策略的一个补充,让同源策略更加的灵活。因此我想优先聊一聊同源策略的概念。

为了安全,浏览器都会支持同源策略,主要有如下几个作用:

禁止两个互不相关的站点互相访问cookie

如果没有限制将会特别可怕,参考如下场景:

  1. A想在网上银行进行转账操作,登录成功后服务器会在浏览器下域名为Bank.com的cookie中存放一个token。
  2. 以后每次访问该网上银行,都会将token传给服务器,服务器验证通过,就代表登录成功。
  3. 此时B给A发送了一个不可描述的网站xxx.com,A激动的快速打开该网站
  4. 但不曾想,xxx网站竟然在暗地里调用该银行的转账接口,向某境外账户转了1块钱。

正常转账接口都会做token验证,但在刚才的栗子中,由于没有同源策略的限制,xxx.com网站可以轻松获取Bank.com下的token,从而能够成功转账。

禁止A域的网站访问B域的资源

其实上面的栗子有个前提,那就是在xxx网站可以访问Bank的转账接口,而这个前提也是被同源策略进行管理的。

禁止与网站与iframe之间的Dom查询

如果这个被开放了,那就厉害坏了,参考如下场景:

  1. 早上醒来发现有一封邮件,提示你信用卡异常消费,让你点击邮件里提供的网站

    banik.com

    (真实官网

    bank.com

    )网站查询
  2. 你按照他的引导进入

    banik.com

    ,登录查看后发现原来是虚惊一场,并没有什么异常消费,接着高高兴兴的上班去了
  3. 到了晚上,有短信提示你进行一笔大额转账,抓紧登录

    bank.com

    查看,发现钱真的被转走。???

banik.com

是诈骗邮件提供的一个钓鱼网站,这个网站比较简单,共有两部分:

  1. 一个全屏iframe标签并加载真实官网

    bank.com

  2. 一个获取iframe里账号密码输入框节点的脚本

在没有该同源策略的限制下,

banik.com

可以访问到

bank.com

里的dom节点,接着获取你输入的账号密码岂不轻而易举。

理解跨域

之前提到过我对跨域的理解:跨域是对同源策略的一个补充,让同源策略更加的灵活。现在我们就聊聊都跨域对同源策略都做了哪些补充。(有其他想法的欢迎留言补充)

在保证安全的基础上,让A域网站可以访问B域资源

下面通过问答的方式对这一点进行分解。

  1. 为什么会出现A域网站访问B域资源的情况
    1. 对于小站点,把网站的js、css、图片等资源放在同一个服务器是没有问题的,但当网站的体量越来越大,用户量越来越多时,对服务器的压力会倍增,从而影响服务器的响应效率。因此我们需要将资源放在不同的服务器,从而产生了跨域的需求。
    2. 随着前端的繁荣发展,演变出很多优秀的第三方服务,而这些第三方服务肯定跟公司的域名不一致,因此产生也产生了跨域需求
  2. 有哪些官方跨域方式
    1. 在早期,官方就意识到上面说的第一个问题,因此让

      script

      img

      标签都支持跨域
    2. 后来针对第二个问题,专门推出一个W3C标准:CORS(Cross-origin resource sharing),后面详解
  3. 有哪些非官方跨域
    指的就是利用一些奇淫技巧实现跨域,主要有如下三种
    1. JSONP - 基于script标签

      仅支持GET请求,可以获取服务器的响应数据

    2. img - 基于img标签

      仅支持GET请求,无法获取服务器的响应数据

    3. iframe - 基于iframe标签

      支持各种请求方式,无法获取服务器的响应数据

在保证安全基础上,让A域的网站可以访问B域的cookie

在同源策略中,是明确禁止这种行为的,但上有政策,下有对策,可以采用曲线救国的方式来实现,各种广告联盟就是这种做的,参考如下场景:

  1. 我有一个日访问量达到百万的网站

    juzi.com

    ,想接入JD的广告联盟赚点外快,各种流程走完以后我拿到如下代码,并且将该代码嵌入到我网站的广告模块(你也可以直接在浏览器中访问下面iframe指定的链接,效果是一样的)
  2. 发版以后,想看看效果。于是我去京东官网搜索了两个关键词

    婴儿车

    iphone6

    ,接着刷新我的网站,发现广告模块开始给我推送

    婴儿车

    iphone6

    的商品。

在该栗子中,最为神奇的点在于,一个跟京东完全不相关的网站怎么知道你在京东访问过什么。接下来开始揭秘:

  1. 首先要申明一点,除了京东提供给我的广告代码,在

    juzi.com

    网站中没有任何直接或间接的与京东有任何交互操作。
  2. 关键在于京东给我的那些代码,一个iframe标签并指定其src指向京东的二级域名,由于iframe支持子域名与根域名互相访问cookie,因此在橘子里的iframe向京东发起请求时,会带上你刚才在访问京东官网时留下的cookie,所以我

    juz.com

    中的京东联盟广告可以知道你想要的内容。
  3. 至于iframe是干嘛的,就不再这里啰嗦了。

跨域实践

在本文中我只提第一种场景:在保证安全的基础上,让A域网站可以访问B域资源,至于第二种,通过广告联盟的栗子便可知道怎么做我就不做过多描述。

接下来我们就按照跨域方式出现先后顺序挨个实践

标签跨域

为了缓解服务器压力,w3c标准让

script

img

标签支持跨域操作,因此我们可以将将接口请求直接放到这类标签中,另外为了操作更加灵活,一般使用动态方式创建这两种标签

优势

不需要后端配合

劣势

  1. 仅仅支持GET请求
  2. 不能够获取响应的数据

实践

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

标签

优势

可以获取响应的数据

劣势

  1. 仅支持get
  2. 需要后端配合

实现原理

前端

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跨域有如下两种,本小节说的是第二种

  1. 像广告联盟那样跨域获取cookie,
  2. 发送跨域请求

优势

此方式是搭配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'
  }
})
           

上诉代码看懂需要两个前提条件:

  1. 理解了广告联盟的运作机制
  2. 理解iframe和form标签,尤其要知道form标签的target属性是干嘛的

CROS跨域

CROS全称Cross-origin resource sharing,想要做详细了解的,请移步查看阮一峰的跨域资源共享 CORS。下面我通过问答的方式记录一下我学习cros过程中碰到的疑问。

  1. cros是谁的功能,网络库?
    CORS是一个W3C标准,是每个浏览器要遵守的规范
  2. 预检请求是谁发出的?
    客户端发送一个请求,浏览器会做拦截,判断是否是跨域请求,如果时就主动向服务器发出一个opitons请求
  3. 是谁在响应预检请求?
    是我们自己的服务器
  4. 预检请求的Origin字段是浏览器自动带上的,不用自己特意写
  5. 在后端没有写options请求相关接口的情况下,是怎么将数据响应给客户端的?
    查了很多资料都没有查到,目前理解如为:只要服务器支持options请求,就自动将如下的响应头返回,不需要专门做干预
    1. Access-Control-Allow-Origin
    2. Access-Control-Allow-Headers
    3. Access-Control-Allow-Methods
  6. 后端需要做什么事情
    1. 支持options请求
    2. 在支持options请求的地方,根据自己情况设置如下几个字段:
      1. Access-Control-Allow-Origin
      2. Access-Control-Allow-Headers
      3. Access-Control-Allow-Methods
      4. Access-Control-Allow-Credentials
  7. 预检请求的响应流程
    扒一扒各种跨域的原理理解同源策略理解跨域跨域实践

代理跨域

虽然浏览器有跨域限制,但服务器没有啊。因此可以通过自己搭建服务器A,让前端访问A服务器,服务器A再访问想要访问的接口来实现跨域。

继续阅读