浏览器同源策略

目前的 web 开发还相当的依赖 cookie , 而cookie的使用限制于浏览器的同源策略(same-origin policy), 同时这个策略也是保证我们网站信息安全的基础, 这篇文章主要了解一下浏览器同源策略具体的含义, 以及在实际开发中如何绕过这一限制来达到跨域请求数据的目的.

# 何为同源

一个访问地址大致可以分为<协议><域名><端口><路径>四个部分: 例如https://www.example.com:80/home/index.html?page=3, https为协议, www.example.com为域名, 域名内也可分为顶级域名, 一级域名, 二级域名, 三级域名等等, 具体如何拆分的讨论可以参考知乎的一个帖子, 你只要明白意思就可以了, 这里的话我采用其中一种说法来描述一下, com为顶级域名, example为一级域名, www为二级域名, 接下来的80为端口, /home/index.html?page=3为路径, 这样我们就把一个URL的各个部分拆分开了.

所谓同源, 就是限制了<协议><域名><端口>这三个部分必须要一模一样, 此时就称为同源, 如果这三块有任何一个地方不一样就产生了跨域.

# 跨域带来的限制

在跨域情况下有如下几种情况会受到限制:

  1. 两个域之间无法互相读取Cookie、LocalStorage 和 IndexDB 例如a.com向api.com发起请求的时候, 请求中默认是不会携带a.com域下的cookie的, 也就是说api.com默认情况下是无法获取a.com的cookie的
  2. 两个域之间无法互相获取和操作DOM 例如a.com的一个页面内有一个iframe, iframe里面加载的是b.com的一个一个页面, 那么这两个页面之间是无法获取另一方的DOM来进行操作的
  3. 两个域之间无法使用ajax通信 例如a.com向api.com发起一个ajax请求(XMLHttpRequest或者fetch()), 那么返回的数据默认会被浏览器拦截并且丢弃然后控制台提示产生一个跨域的错误, 所以在js中是无法拿到返回数据的

# cookie的概念

这里我们首先看一下cookie的相关概念, cookie是存在客户端浏览器的一小段文本, 不同的浏览器对cookie的大小有不同的限制, 可以通过document.cookie来获取本域名下的cookie信息, cookie中包含如下属性: key=value, Domain, Path, Expires, Max-Age, SecureHttpOnly, 还有用的比较少的HostOnly等, 可以查看这篇文章, 各个属性之间用英文分号和空格("; ")连接, 大概说一下各个属性.

有两种方式产生cookie, 一种是服务器的响应, 而是客户端的js.

// 生成一个cookie
Cookie cookie = new Cookie("name", "krics");
cookie.setPath("/");
cookie.setDomain(".example.com");//这样设置,能实现不同二级域名的两个网站共用这个cookie, 自定义
cookie.setMaxAge(365 * 24 * 60 * 60);// 不设置的话,则cookies不写入硬盘,而是写在内存,只在当前页面有用,以秒为单位
response.addCookie(cookie);         //添加第一个Cookie
// 可以重复上面的代码添加多个cookie

这样就会在响应头中添加上面示例给出的额外的头信息.

# 解决跨域

这里可以参考阮一峰的文章浏览器同源政策及其规避方法.

# iframe形式的跨域

  1. 如果两个窗口一级域名相同,只是二级域名不同,那么可以通过设置document.domain属性为同一个值,就可以规避同源政策,拿到DOM。
  2. 片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://example.com/x.html#fragment的#fragment。如果只是改变片段标识符,页面不会重新刷新。但是会触发windowonhashchange事件, 可以通过监听这个事件来传递数据.
  3. 监听window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它, 而且这个值得容量很大, 字符串形式.
  4. 以上三种方式都属于hack, HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging),这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。具体使用可以参考MDN, 需要注意的是在IE下有一定的兼容性问题.
  5. 通过window.postMessage,读写其他窗口的 LocalStorage . 也是基于window.postMessage的.

# AJAX

这里才是重点, 一个跨域的AJAX请求要想正常交互, 有如下四种解决方案:

  1. JSONP 它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。 这种方式的优点是简单兼容性好, 服务器的改造小, 但是这种方式因为是通过加载js脚本的形式实现的, 所以只支持GET方式.
  2. WebSocket WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。 这种方式对服务器的改造最大, 需要更改协议
  3. Nginx 通过在服务器端布置Nginx, 通过它代理请求, 然后由Nainx将请求转发到真正的数据服务器上, 这种方式需要在服务器端安装Nginx, 然后需要配置Nginx才行
  4. CORS CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。 关于CORS, 可以参考阮一峰的文章跨域资源共享 CORS 详解, 简单来说是按照下文配置.

# CORD

  1. 服务器端配置
  1. 客户端配置 withCredentials: 代表跨域请求是否发送对应域的cookie, 可选字段, 默认不会发送(有的浏览器可能例外, 也可以显示的设置为false), 设置为true代表发送, 需要注意的是客户端是需要和服务端配合使用的, 当设置为true时, 虽然cookie发送过去了, 但是服务器要配置Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin: www.example.com才能正确接收到cookie

当然还有更详细的内容, 如果碰到更多问题, 可以去看一下上面阮一峰的关于跨域的文章, 也可以参考Faremax的一片文章全解跨域请求处理办法.

# 参考文章

聊一聊 cookie 全解跨域请求处理办法 跨域资源共享 CORS 详解