Lasy

前端开发攻城狮~

浏览器同源策略及规避方法,跨窗口通信

不能直接跨窗口通信的原因就是同源策略。之所以单独提出跨窗口通信,是因为这是一个比较典型的问题。
其实网上关于同源策略(same-origin policy)的文章已经很多了,但是或者不够全面,或者条理不够清晰,导致阅读起来比较麻烦。我在这里做一个比较全面、条理清晰的总结。

同源策略定义

Wikipedia上的定义:

In computing, the same-origin policy is an important concept in the web application security model. Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, host name, and port number. This policy prevents a malicious script on one page from obtaining access to sensitive data on another web page through that page’s Document Object Model.

翻译过来:

在计算中,同源策略是Web应用程序安全模型中的一个重要概念。根据该策略,网络浏览器允许第一个网页中包含的脚本访问第二个网页中的数据,但前提是两个网页具有相同的源。源被定义为URI,主机名和端口号的组合。此策略可防止一个页面上的恶意脚本通过该页面的文档对象模型访问另一个网页上的敏感数据。

对于同源,我们可以简单地理解为:
同源即:协议、域名、端口都相同。
同源策略可以分为两种类型:

  1. 不同窗口之间的同源策略(跨窗口通信问题)。
  2. 客户端与服务器之间的同源策略

不同窗口(这里的窗口包括不同的标签页,iframe)之间的同源策略,会造成如下限制:

  • 不同窗口之间不能互相操作Cookie, DOM。
  • 不同窗口之间不能互相操作LocalStorage, IndexDB。
  • 不同窗口之间不能互相传递数据。

客户端与服务器之间的同源策略,会造成如下限制:

  • AJAX请求不能发送,也就是说不能发送跨域请求

同源策略的规避方法

一、不同窗口之间的同源策略(跨窗口通信)

  • 不同窗口之间操作Cookie, DOM的方法。
    说明:不同窗口之间如果要操作Cookie, DOM,前提是窗口的一级域名相同。如果一级域名不同,不存在操作Cookie, DOM的方法,这些都是敏感数据,出于安全考虑,毕竟你也不想别的网站拿到你访问银行网站的数据吧。

    • 将两个窗口的document.domain设置成相同的域名

      将不同源的窗口变为同源窗口,因而便不再受同源策略限制。
      关于设置成什么域名,只要是两个窗口的域名中都包含的域名就行,一般设置为一级域名即可。

      如,http://a1.lasy.site/page1.html 和 http://a2.lasy.site/page2.html,在两个网页中分别设置

      • 读取Cookie
        page1

        page2

        page1设置cookie,page2就可以直接通过document.cookie来读取cookie(这种方法原理其实是由于domain相同,所以两个窗口的Cookie就被设置为同一个Cookie,),从而实现不同窗口之间读取cookie。
        对于Cookie设置成相同的一级域名还有另外一种方法:服务器设置Cookie域名。
      • 读取localStorage
        page1

        page2

        与cookie同理,操作的是同一个localStorage。
      • 读取indexDB
        page1在indexDB存储数据
        page2

        与cookie同理,操作的是同一个indexDB。
      • 读取DOM
        page2

        由于设置了相同的domain,page2才能访问到page1的document的内容(DOM)。
  • 不同窗口之间操作操作LocalStorage,IndexDb。
    • window.postMessage + iframe
      page1要操作page2的localStorage,则由page1建立一个iframe并将src设置为与page2同源,然后通过postMessage方法将操作传递给iframe,再由与page2同源的iframe操作page2的localStorage
      page1

      www.page2.com/getmessage.html

      IndexDB同理。
  • 不同窗口之间传递数据
    • window.name

      window.name属性无论是否同源,都可以读取到。
      page1

      page2

    • 片段识别符(fragment idetifier)

      片段识别符也就是我们所说的锚点。网址后面增加锚点,并将数据存入锚点,另一个窗口通过锚点来取得数据。
      page1

      page2

    • window.postMessage

      前面两种方法本质上都是一种hack,违背了同源策略,但是却通过hack的方式访问到了不同源的资源。
      而window.postMessage方法是官方提供的方法。window.postMessage() 方法可以安全地实现跨域通信。
      page1 域名: http://lasy.site

      page2 域名: http://other.site

二、客户端与服务器之间的同源策略

规避客户端与服务器之间的同源策略也就是说要发送跨域请求。以下是方案:

  • 架设代理服务器

    浏览器将请求发送至同源服务器,同源服务器再向其他不同源的服务器发送请求。

  • JSONP

    JSONP是最常用的发送跨域请求的方法,由于script标签不受同源策略的影响,因此可以利用script标签向不同源的服务器发送请求,具体做法:
    在网页中加入script标签,将src设置为服务器地址,并加上回调函数名字。

    不过,上述过程一般是通过js动态添加的。
    然后编写回调函数。

    服务端也要配合返回一个调用回调函数格式的内容,并将数据写入函数参数的位置。
    服务端要返回的内容:

    也可以是对象:

    客户端便可以收到这个数据。
    原理详解:
    对于正常情况

    script标签会向src属性中的服务器地址发送请求,服务器会将js代码以文本的形式传输给客户端,客户端接收到之后,立即执行收到的js代码。
    服务器的返回的代码中如果包含了一个在客户端里定义的函数,浏览器也会执行这个函数。函数的定义决定了这个函数要对函数参数做的事情;函数的调用则决定了传入的参数是什么。这个过程刚好和客户端与服务器对应起来,客户端决定要拿数据做什么事情,服务端决定要返回什么数据。因此,我们通过在客户端定义函数,在服务端返回执行这个函数的代码,并将数据写入函数参数,那么客户端就可以接受到这个数据了。这个方法很巧妙,但是有一个问题,服务器如何知道这个函数是什么呢?也很简单,在请求的地址中加入参数,把函数名字作为参数发送给服务器不就好了,因此才有了这样的写法:

    callback是客户端与服务器约定的参数名,func是客户端定义的函数名,也就是服务端要写入代码中执行的函数名。

  • CORS

    CORS是跨源资源分享(Cross-Origin Resource Sharing),是一个W3C标准。
    有了CORS,客户端进行跨域请求的时候十分简单,只需将之前AJAX请求时的相对路径修改为绝对路径即可,即加上协议、域名、端口(如果不是默认端口)。

    但是CORS需要服务端的支持。
    服务端需要在回应中加入Access-Control-Allow-Origin字段,并且其值中要包含发送请求的域名。如:

    如果没有,服务端不会返回响应的数据。
    CORS目前所有浏览器都支持,iE浏览器要在10+。
    更详细的CORS的介绍,可以参考阮一峰的这篇文章
    JSONP和CORS的比较:

    • JSONP由于使用的是script标签,因此只能使用GET方法;CORS支持所有类型的请求。
    • JSONP兼容性更好;CORS在IE浏览器上只支持10+。
    • 因script标签会将资源作为 JS 代码执行,所以可能会被注入恶意代码;CORS安全性更好。
    • CORS操作更简单。
  • WebSocket

    WebSocket是一种通信协议,只要服务端支持且域名包含在Access-Control-Allow-Origin中,就可以进行跨域请求。
    但是WebSocket的诞生本质上就是为了解决HTTP协议本身的单向性问题,比较适合于双向通信的场景,但对于Request-Response的模式,不推荐使用WebSocket。

总结

window.postMessage()方法是窗口间通信最常用的方法,也是规避窗口间同源策略最好的方法。
JSONP和CORS是规避客户端和服务端同源策略的最好方法。

参考链接:

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注