面试时谈谈:关于跨域

298 阅读8分钟

什么是同源限制?

同源策略限制指:从一个源加载的文档或者脚本与来自另一个源的资源进行交互时会被浏览器所限制,这是一个用于隔离潜在恶意文件的关键的安全机制。

同源:保证协议、域名、端口三个相同

限制:有以下三种行为的限制
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。

同源策略又分为以下两种:
DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。(页面与页面之间的限制)
XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。(浏览器和服务器之间的访问限制)

为什么要有同源限制?

为了保证用户信息的安全,防止恶意的网站窃取数据。
举例:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。

如何实现跨域?

方法1:jsonp(解决ajax请求同源限制)

原理

<script>标签不受同源策略限制,允许跨域引用资源。可通过src属性来进行ajax请求

如何做?

  • 动态创建一个 script 标签
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
  • 请求参数写入url中,并和服务器协商回调函数的名称
script.src = 'http://www.example.com/json?key1=v1&key2=v2&callback=handleResponse';
body.appendChild(script);

function handleResponse(data) {
    console.log(data);
};

有什么问题?有什么需要注意的?

优点:简单,没有兼容问题,通用。
缺点:只能发送GET请求,不安全,获取异常困难(onerror兼容性问题),回调必须为全局方法

方法2:cors(解决ajax请求同源限制)

原理

CORS(Cross-origin resource sharing,跨域资源共享)使用自定义的 HTTP头部让浏览器与服务器进行沟通是否支持跨域。

如何做?

简单请求(simple request)

同时满足:请求方法为HEAD、GET、POST中一种,请求头字段不超过Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(只限于application/x-www-form-urlencoded、multipart/form-data、text/plain)

  • 请求附加一个额外的 Origin 头部,其中包含请求页面协议、域名和端口,和服务器协商是否支持响应。例如:Origin: www.example.com:8080
  • 如果服务器接受,就在 Access-Control-Allow-Origin 头部中回复。例如:Access-Control-Allow-Origin:www.example.com:8080,否则驳回请求
  • 以上过程不包含cookie信息,如果需要cookie,ajax 请求需要设置 xhr 的属性 withCredentials 为 true,服务器需要设置响应头部 Access-Control-Allow-Credentials: true。

非简单请求(not-so-simple request)

不同时满足简单请求的条件的

  • 发送真正请求前,先发送options请求(预请求),请求头包括Origin、 Access-Control-Request-Method、 Access-Control-Request-Headers(可选,自定义的头部信息,多个头部以逗号分隔)。
  • 服务器预响应,回复是否对以上请求支持。响应头包括Access-Control-Allow-Origin、 Access-Control-Allow-Methods(允许的方法,多个方法以逗号分隔)、 Access-Control-Allow-Headers(允许的头部,多个方法以逗号分隔)、 Access-Control-Max-Age(将请求缓存多长时间/秒)。
  • 之后同简单请求

有什么问题?有什么需要注意的?

注意:CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。
优点:简单,和同源ajax请求相似,支持所有类型http请求。
缺点:兼容性、预请求

方法3:document.domain(解决读取cookie同源限制)

原理

处理主域名相同,而子域名不同的跨域(例如http://www.example.com/a.html和http://example.com/b.html之间),适用于iframe跨域

如何做?

  • 两个页面都显式设置document.domain为同一个域名
//a.html中
<iframe src="http://example.com/b.html" id="myIframe" onload="test()">
<script>
    document.domain = 'example.com'; // 设置成主域
    function test() {
        console.log(document.getElementById('myIframe').contentWindow);
    }
</script>
//b.html中
<script>
    document.domain = 'example.com'; // document.domain 设置成与主页面相同
</script>

这样http://www.example.com/a.html就可以通过 js 访问到http://example.com/b.html 中的各种属性和对象了,共享Cookie。

有什么问题?有什么需要注意的?

注意:document.domain 的设置是有限制的,只能把 document.domain 设置成自身或更高一级的父域,且主域必须相同

方法4:window.name(解决获取dom同源限制)

原理

在一个窗口(window)的生命周期内,窗口载入的所有的页面(不管是相同域的页面还是不同域的页面)都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

如何做?

  • 在跨域页面上写入window.name
//b.com/index.html中
<script type="text/javascript">
    // 给当前的 window.name 设置一个 http://a.com/index.html页面想要得到的数据值 
    window.name = "hello world!";
</script>
  • 利用一个隐藏的iframe嵌入到获取数据的页面,在iframe载入后会设置window.name,再将iframe内跨域页面跳转到本页面同源,此时window.name不会改变,并且我们也可以通过contentWindow获取到window.name了。
//a.com/index.html中
<iframe src="http://b.com/index.html" id="myIframe" onload="test()" style="display: none;">
<script>
    // iframe载入http://b.com/index.html 页面后会执行该函数
    function test() {
        var iframe = document.getElementById('myIframe');
        //第二次onload,此时iframe域已经被更改为与该页面在同一个源了,可以相互访问了
        iframe.onload = function() {
            var data = iframe.contentWindow.name; // 获取 iframe 里的 window.name
            console.log(data); // hello world!
        };
        
        // 重置一个与 http://a.com/a.html 页面同源的页面
        iframe.src = 'http://a.com/c.html';
    }
</script>

有什么问题?有什么需要注意的?

注意:让iframe内的跨域页面,设置了window.name之后,必须跳转到与iframe外页面同域的页面,否则通过$('iframe').contentWindow.name访问会报跨域错误。

方法5:location.hash (解决获取dom同源限制)

原理

在url:a.com#helloword 中的‘#helloworld’就是location.hash,改变hash并不会导致页面刷新,所以可以利用hash值来进行数据传递。

如何做?

  • a.com/index.html要和b.com/index.html传递信息,首先创建一个隐藏的iframe,这时的hash值可以做参数传递用。
//a.com/index.html中
<iframe src="http://b.com/index.html#paramdo" id="myIframe" onload="test()" style="display: none;">
<script>
    function test() {
        var data = window.location.hash;
        console.log(data);
    }
</script>
  • b.com/index.html响应请求后再将通过修改a.com/index.html的hash值来传递数据
//b.com/index.html
<script type="text/javascript">
    switch(location.hash){
        case '#paramdo':
            callBack();
            break;
        ...
    }
    function callBack() {
        // 设置父页面的 hash 值
        parent.location.hash = "world";
    }
</script>

有什么问题?有什么需要注意的?

缺点:传递数据容量有限,数据直接暴露在url上

方法6:postMessage(解决获取dom同源限制)

原理

H5新api,实现window对象之间的跨域通信。window.postMessage() 方法被调用时,会在所有页面脚本执行完毕之后(例如:在该方法之后设置的事件、之前设置的timeout 事件)向目标窗口派发一个 MessageEvent 消息。目标窗口通过监听message事件来获取传递的数据。

如何做?

//a.com/index.html中
 <iframe src="http://b.com/index.html" id="myIframe" onload="load()" style="display: none;"></iframe>
 <script type="text/javascript">
  function load(){
   let frame = document.querySelector('#myIframe');
   //该方法的第一个参数 message 为要发送的消息,类型只能为字符串;
   //第二个参数 targetOrigin 用来限定接收消息的那个 window 对象所在的域,
   //如果不想限定域,可以使用通配符 *
   frame.contentWindow.postMessage('test','*')
  }
  window.onmessage = function(e){
   console.log(e.data)
  }
 </script>

//b.html
 <script type="text/javascript">
  window.onmessage = function(e){
    e = e || event;
    //e.origin获取发射源的域,e.data获取传递数据 
   console.log(e.origin,e.data);//http://a.com/index.html,test
   //向父级(发射源)发送消息
   e.source.postMessage('test1','http://a.com/index.html');
  }
 </script>

有什么问题?有什么需要注意的?

注意:h5的兼容问题

最后,面试官可能就最近chrome80的新变更问问samesite和跨站相关

跨站和跨域的区别

跨域和跨站是完全不同的概念,自我理解为,跨域是相对于浏览器同源策略限制来说的,而跨站这个概念相对于是否携带cookie来说的。

冴羽大佬博文中:同源策略作为浏览器的安全基石,其「同源」判断是比较严格的,相对而言,Cookie中的「同站」判断就比较宽松:只要两个 URL 的 eTLD+1 相同即可,不需要考虑协议和端口。其中,eTLD 表示有效顶级域名,注册于 Mozilla 维护的公共后缀列表(Public Suffix List)中,例如,.com、.co.uk、.github.io 等。eTLD+1 则表示,有效顶级域名+二级域名,例如 taobao.com 等。
举几个例子,www.taobao.com 和 www.baidu.com 是跨站,www.a.taobao.com 和 www.b.taobao.com 是同站,a.github.io 和 b.github.io 是跨站(注意是跨站)。

samesite

samesite属性用来设置Cookie 在跨站请求时是否被发送,跨站请求不发送cookie可有效阻止CSRF攻击。samesite属性各值含义:

  • Strict:仅允许一方请求携带 Cookie,即浏览器将只发送相同站点请求的 Cookie,即当前网页 URL 与请求目标 URL 完全一致。
  • Lax:允许部分第三方请求携带 Cookie
  • None:无论是否跨站都会发送 Cookie

chrome80的samesite相关更新

只有HTTPS、且SameSite设置为None时才允许第三方调用Cookie。SameSite属性之前默认是 None 的,Chrome80 后默认是 Lax。需手动设置为none。

alt chrome80更新后使用第三方调用cookie方式比较