浏览器同源策略及跨域知识详解

515 阅读7分钟

背景

在聊正文之前,我们先来了解一下web的发展史,大概了解一下web安全的演变历程。

       1989年,蒂姆·伯纳斯-李爵士设计发明了第一个浏览器,架设了第一个 web 服务器 info.cern.ch。那时候的浏览器就是非常简单的理念,就是知识共享,输入正确的网址你就能获取到相应的内容。

       随着时代发展,浏览器的功能也越来越强大,从最开始的只能阅读文档,到后面可以查看图片,音频等,互联网不再只是用于简单的浏览文档信息、查看门户网站这些。
这时候,交互式web开始兴起,如用户登录,购买商品,各种论坛等,于是乎人们发现了一些问题,客户端和服务器的传输使用的是http协议,http协议是无状态的,那么服务器就不知道这一次请求的人,跟之前登录请求成功的人是不是同一个人

由于http协议的无状态,服务器忘记了之前的所有请求,它无法确定这一次请求的客户端,就是之前登录成功的那个客户端。

于是cookie出现了

Cookie

Cookie是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息,用于服务器记录客户端的状态。

cookie在行使自身使命的同时,也存在了安全隐患。

设想一下,你登录了银行系统,并把登录信息、账号密码存在了cookie中,cookie信息共享,大家的账号和密码也共享了,奋斗了一辈子的财富,被别人轻易的转走,将是多么可怕的事情。所以,为了维护互联网的隐私和数据安全,必须制定一定规则。这就是同源策略

同源策略

同源策略的概念要追溯到1995年的网景浏览器。作为一个重要的安全基石,所有的现代浏览器都在一定程度上实现了同源策略。

在这个策略下,web浏览器允许第一个页面的脚本访问第二个页面里的数据,但是也只有在两个页面有相同的源时。

而所谓的源是由协议,主机名,端口号组合而成的。这个策略可以阻止一个页面上的恶意脚本通过页面的DOM对象获得访问另一个页面上敏感信息的权限,同一个内的脚本只能读写本域内的资源,**对于其它域的资源,它没有禁止脚本的执行,而是禁止读取HTTP回复,**这种安全限制称为同源策略。

限制范围

无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB。
无法接触非同源网页的 DOM。
无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)

同源策略提升了Web前端的安全性,但阻碍了Web拓展的灵活性。**若把html、js、css、flash,image等静态文件全部布置在一台服务器上,会加大这台服务器的压力,甚至威胁到web服务的可用性。**因此,在遵循同源策略的基础上,在安全性和可用性之间选择了一个平衡点。以下这些标签不受同源策略的影响

* <img src=XXX>
* <link href=XXX>
* <script src=XXX>
* <iframe>

虽然有上述对同源策略的补充,但是有时候我们不得不需要访问其它域的资源(例如服务器集群)所以就有了跨域的解决方案

跨域解决方案

首先我们需要知道跨域浏览器的同源策略下的产物,所以服务端不存在跨域的问题,服务器间的调用,一般是为了获取接口数据的,功能单一。app也相对比较自由,可自行设置白名单等限定条件。

1、通过jsonp跨域(get请求)

JSONP 是 JSON with Padding 的略称。它是一个非官方的协议,利用<script> 标签没有被同源策略限制,向网页中动态插入script标签,向服务端发送请求,并允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样前端可以随意定制自己的函数来自动处理返回数据了。

显而易见,JSONP并不是一个好的跨域解决方案,它至少有着下面两个严重问题:

  1. 它需要服务器端支持,但在非跨域的情况下,服务器又需要响应一个正常的JSON格式
  2. 只能发送 get 请求,script元素发出的请求只能是get请求

2、CORS跨域

CORS是基于http1.1的一种跨域解决方案,它的全称是Cross-Origin Resource Sharing,跨域资源共享。它也是目前应用最为广泛的一种解决方案。

它的总体思路是:如果浏览器要跨域访问服务器的资源,需要获得服务器的允许

而要知道,一个请求可以附带很多信息,从而会对服务器造成不同程度的影响,比如有的请求只是获取一些新闻,有的请求会改动服务器的数据,针对不同的请求,CORS规定了三种不同的交互模式,分别是:

  • 简单请求

       请求方法是 get post head

       请求只包含这些字段 Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width

        请求头content-type只包含 text/plaiin、multipart/form-data、application/x-www-form-urlencoded

  • 需要预检查的请求

  • 附带身份证的请求

当浏览器判定某个ajax跨域请求简单请求时,会发生以下的事情

  1. 请求头中会自动添加Origin字段

  2. 服务器响应头中应包含Access-Control-Allow-Origin

判定为预检查请求

  1. 浏览器发送预检请求,询问服务器是否允许

    OPTIONS /api/user HTTP/1.1 Host: crossdomain.com ... Origin: my.com Access-Control-Request-Method: POST Access-Control-Request-Headers: a, b, content-type

  2. 服务器允许

HTTP/1.1 200 OK
Date: Tue, 21 Apr 2020 08:03:35 GMT
...
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: a, b, content-type
Access-Control-Max-Age: 86400
...

   3.浏览器发送真实请求

  1. 服务器完成真实的响应

判定为附带身份查请求

// xhr
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

// fetch api
fetch(url, {
  credentials: "include"
})

当一个请求需要附带cookie时,无论它是简单请求,还是预检请求,都会在请求头中添加cookie字段

而服务器响应时,需要明确告知客户端:服务器允许这样的凭据

告知的方式也非常的简单,只需要在响应头中添加:Access-Control-Allow-Credentials: true即可

对于一个附带身份凭证的请求,若服务器没有明确告知,浏览器仍然视为跨域被拒绝。

另外要特别注意的是:对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为*

3、document.domain + iframe 跨域(主域相同,子域不同, 设置document.domain为基础主域)

4、location.hash + iframe 跨域(不同域之间利用iframe的location.hash传值)

5、window.name + iframe 跨域(name值[2MB]在不同的页面加载后依旧存在)

6、postMessage 跨域 (跨文档通信)

7、跨域资源共享(CORS)(W3C 标准,跨源ajax请求的根本解决方式)

8、nodejs中间件代理跨域(服务器向服务器请求)

9、nginx代理跨域(类似于node中间键,使用中转nginx服务器来转发请求)

10、WebSocket协议跨域 (利用双向通信协议)