跨域知识体系解析

909 阅读8分钟

前言:跨域涉及内容较多,也是面试必备知识。本文希望将跨域的技术点都串联起来,形成一个知识体系。

1.什么是跨域?

跨域定义:协议(http和https)、域名、端口号任意一个不同,都属于跨域。

网址:www.foo.com/page?name=1…

构成:<scheme>://<netloc>:<port>/<path>?<query>#<fragment>

解释:协议://域名:端口号/路径

https://www.foo.com/page?name=123#card:不同源,协议不同

http://www.foo.com:8081/page?name=123#card:不同源,端口号不同

http://foo.com/page?name=123#card:不同源,域名不同

http://aaa.foo.com/page?name=123#card:不同源,域名不同

http://www.aaa.com/page?name=123#card:不同源,域名不同 http://www.foo.cn/page?name=123#card:不同源,域名不同

2.为什么要设置跨域?

因为跨域的存在,我们需要额外配置很多信息,比如http请求中的headers,尤其是做低版本浏览器(如IE8、IE9)兼容时,跨域是一件很麻烦的事情。所以当初的设计者为什么要设置跨域呢?

以下摘抄自MDN:

出于安全原因,浏览器限制了从脚本启动的跨域HTTP请求。

那如果没有跨域的话,会导致什么问题呢?

  • DOM同源策略:如果iframe之间可以跨域访问,会出现什么问题呢?

    • 做一个假网站aaa.com,用iframe嵌套一个银行网站https://www.bank.com
    • iframe最大化,做成和银行网站一样的样式
    • 用户点进假网站aaa.com后,输入用户名、密码
    • 假网站就可以跨域访问到https://www.bank.cominput输入框,拿到用户名和密码,个人信息就完全被泄露了
  • AJAX请求同源策略:如果所有请求之间,没有同源策略限制,会发生什么?

    • 用户登录某银行网站http://www.aaa.com后,网站A会向用户的cookie添加一个唯一id标识
    • 然后,用户又打开了一个网站Bhttp://www.bbb.com,执行了网站里的恶意代码,向网站A发起了ajax请求,请求会默认将用户的cookie带过去
    • 此时A网站核实用户信息无误,response返回用户数据,数据就会被泄露

现在就能理解MDN中的那句话了:同源策略让用户信息更安全

非同源情况下,会有三种行为受到限制:

AJAX请求

DOM访问

cookie、localStorage、IndexDB无法获取

AJAX请求的跨域解决方案

  • 图片资源
    • 图片资源不受跨域限制。
    • 方法:监听onload和onerror,确定是否接收到了响应。
    • 实现:动态创建<img>,在onload和onerror方法中监听数据,判断处理请求是否成功
    • 用途:跟踪用户点击页面或动态广告曝光次数。但只能发送get请求,无法访问服务器的responseText,只能用于服务器与浏览器的单向通信。
  • JSONP
    • 原理:script和img元素,都能不受限制的从其他域加载资源。
    • 实现:动态创建<script>标签,添加src属性url?callback=funcName
    • 服务器端收到请求后, 会把数据放在callback函数的参数位置中返回,由于是<script>请求的脚本,因此可以直接运行。
    • 返回参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
    • 缺点:从其他域加载代码,可能会造成安全问题,无法确定请求是否失败。 且只能发送GET请求。
// 动态创建<script>标签
function handleCallback(res) {
    console.log(res);
}
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = 'url?callback=handleCallback';
document.body.appendChild(script);
  • CORS
    • 解决跨域AJAX请求的根本方法。需要浏览器和服务器同时支持,IE>=10版本。
    • 原理:用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求响应的结构。给一个请求附加一个额外的origin头部,服务器返回的Access-Control-Allow-Origin头部中是否包含请求域或为*,则请求允许,否则不允许。
    • 备注:跨域请求和响应都不包含cookie。
    • 优点:可以发送任何形式的请求,GET \ POST等。而JSONP只能发送GET请求。相比XMLHttpRequest请求,CORS通信与同源的AJAX通信没有差别,代码完全一样。
    • CORS通信分为简单请求复杂请求
    • 简单请求
      • 仅限:HEAD \ GET \ POST方法
      • 只包含以下头信息的请求:ACCEPT 、 ACCEPT-Language 、Content-Language 、 Last-Event-ID
      • Content-Tyype只能是以下几种类型: application/x-www/form-urlencoded \ multipart/form-datat\ text/plain
      • 简单的跨域AJAX请求,仅仅在头信息部分添加一个origin
      • 若回应的源信息,没有包含Access-Control-Allow-Origin字段,则说明请求出错。
      • 若请求的源,在服务器的允许范围内,返回的信息会增加几个头字段
        • Access-Control-Allow-Origin: url
        • Access-Control-Allow-Credenttials: true /false表示cookie可以包含在请求中,一起发送给服务器
        • Access-Control-Expose-Headers: 使CORS请求时,可以通过getResponseHeader()方法拿到的字段
      • 若要在请求时发送cookie和HTTP认证信息,需要客户端和服务器端都同意
        • 服务器端,Access-Control-Allow-Credenttials: true
        • 客户端:xhr.withCredentials = true。
        • 若要发送cookie,Access-Control-Allow-Origin不能设置为*,必须指定明确的、与请求网页一致的域名
    • 非简单请求
      • 请求方法为:PUT/DELETE/PATCHContent-Type不属于下面之一的application/x-www/form-urlencoded \ multipart/form-datat\ text/plain请求,
      • 在正常的HTTP请求之前,添加一个“预检查”请求
      • 预检查请求,方法是OPTIONS,询问服务器,当前域是否在服务器的许可名单之中,以及可以使用那些HTTP动词和头部字段信息,
      • Access-Control-Request-Method
        • 浏览器的CORS请求会用到哪些方法
      • Access-Control-Request-Headers
        • 指定浏览器的CORS请求会发送的额外的头信息字段
      • 预检请求成功以后,会回应一串信息,包含CORS相关的头部信息。
      • 如果不被允许,会返回一个正常的HTTP回应,并且不包含任何的头部信息。触发AJAX请求的onError()函数
      • Access-Control-Max-Age:本次预检查请求的有效期。可用来缓存OPTIONS请求,单位是秒,如果为-1,则表示禁用缓存。
    • CORS的兼容性
      • IE10+实现CORS是使用XMLHttpRequest,但IE8、IE9实现CORS,是使用XDomainRequest
      • XDomainRequest只支持get和post请求,不支持携带cookie,不允许设置自定义headers(不管是否跨域!),response中没有status codeContent-type只能是text/plain,不支持常用的application/json!!!

DOM访问的跨域解决方案

  • DOM跨域:比如网页中嵌入的Iframe,无法通过document.getElementById(),得到Iframe中的DOM。
  • 若两个窗口一级域名相同,二级域名不同时,可以统一设置document.domain属性,让两个网站同源。
  • 完全不同源的网站--解决办法:
    • 片段识别符(fragment identifier)--#
      • 改变url #后面的值不会刷新页面,可以利用hashchange监听事件
      • 父窗口可以把信息写入子窗口的片段识别符,子窗口利用onhashchange监听变化
    • window.name
      • 浏览器窗口下的window.name--可以由同一个窗口下的所有网页读取,无论是否同源
      • 但必须监听子窗口window.name属性的变化,会影响性能
    • 跨文档通信API(Cross-document messaging)
      • HTML5新引入的API---PostMessage:可用于web worker 通信,实现主线程和子线程的通信;也允许来自不同源的脚本,采用异步方式通信;可实现跨文档、多窗口、跨域消息通信。
      • 也可以利用postMessage,监听其他窗口的localStorage
      • 提供方法:postMessage(data, origin)发送消息,监听message事件
     window.addEventListener(‘message’, function(e) {
        console.log(e);
        // e.data:接收到的数据
        // e.souce:发送消息的窗口
        // e.origin:消息发向的网址
    * }, false)
    

cookie跨域的解决方案

  • 一般用于保存用户登录状态,非同源网页不能共享
  • 两个网页一级域名相同,二级域名不同时,可通过设置相同的document.domain,共享cookie。
  • 另外服务器在Set-cookie时,可以直接设置domain为一级域名 domain=.example.com,这样二级和三级域名,不用做任何设置,都可以读取这个cookie。

localStorage、sessionStorage跨域的解决方案

  • localStorage:生命周期是永久,进程被kill后,缓存依然存在,除非手动清除缓存。大小为5MB,仅在客户端被保存,不参与和服务端通信。
    • 跨域:只要不同源就不能共享localStorage的数据。
    • 跨域解决方法postMessage()通信、二级域名共享
  • localStorage跨域——二级域名共享解决办法
    • 假设顶级域名为abc.com,二级域名为www.abc.com,还有其他n级域名或者多个二级域名。
    • 实现方法:输出一个隐藏的iframe加载一个顶级域名的代理页面,统一在顶级域名设置localStorage,其他二级域名或者n级域名需要读取或者设置localStorage时通过此代理ifarme来执行操作。
    • 核心:iframe和当前页面都需要设置document.domain='abc.com',这样就可以相互操作对方了。
  • sessionStorage:关闭页面或关闭浏览器后,缓存消失。大小约为5MB,仅在客户端被保存,不参与和服务端通信。
    • 不同源,或同源但不同页面间无法共享sessionStorage的信息。
    • 如果一个页面包含多个iframe且他们属于同源页面,那么他们之间是可以共享sessionStorage的。
    • 跨域解决方法:sessionStorage生命周期比较特殊,需要用html5的的postMessage来实现,无法使用document.domain

文献参考链接: