同源策略/SOP(Same origin policy)是一种约定,由 Netscape 公司 1995 年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS、CSRF 等攻击。所谓同源是指 "协议 + 域名 + 端口" 三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。
什么是跨域?
当协议、域名、端口号,有一个或多个不同时,有希望可以访问并获取数据的现象称为跨域访问,同源策略限制下 cookie、localStorage、dom、ajax、IndexDB 都是不支持跨域的。
假设 cookie 支持了跨域,http 协议无状态,当用户访问了一个银行网站登录后,银行网站的服务器给返回了一个 sessionId,当通过当前浏览器再访问一个恶意网站,如果 cookie 支持跨域,恶意网站将获取 sessionId 并访问银行网站,出现安全性问题;IndexDB、localStorage 等数据存储在不同域的页面切换时是获取不到的;假设 dom 元素可以跨域,在自己的页面写入一个 iframe 内部嵌入的地址是 www.baidu.com,当在百度页面登录账号密码时就可以在自己的页面获取百度的数据信息,这显然是不合理的。
这就是为什么 cookie、localStorage、dom、ajax、IndexDB 会受到同源策略会限制,下面还有一点对跨域理解的误区:
误区:同源策略限制下,访问不到后台服务器的数据,或访问到后台服务器的数据后没有返回;
正确:同源策略限制下,可以访问到后台服务器的数据,后台服务器会正常返回数据,而被浏览器给拦截了。
实现跨域的方式
一、使用 jsonp 跨域
使用场景:当自己的项目前端资源和后端部署在不同的服务器地址上,或者其他的公司需要访问自己对外公开的接口,需要实现跨域获取数据,如百度搜索。
缺点:
只能发送 get 请求 不支持 post、put、delete;
不安全,容易引发 xss 攻击,别人在返回的结果中返回了下面代码。
会把别人的脚本引入到自己的页面中执行,如:弹窗、广告等,甚至更危险的脚本程序。
二、使用 CORS 跨域
跨源资源共享/CORS(Cross-Origin Resource Sharing)是 W3C 的一个工作草案,定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
使用场景:多用于开发时,前端与后台在不同的 ip 地址下进行数据访问。
现在启动两个端口号不同的服务器,创建跨域条件,服务器(NodeJS)代码如下:
由于我们的 NodeJS 服务器使用 express 框架,在我们的项目根目录下的命令行中输入下面代码进行安装:
npm install express --save
通过访问 http://localhost:3000/index.html 获取 index.html 文件并执行其中的 Ajax 请求http://localhost:4000/getDate接口去获取数据,index.html 文件内容如下:
上面 index.html 代码中发送请求访问不在同源的服务器 2,此时会在控制台给出错误信息,告诉我们缺少了哪些响应头,我们对应报错信息去修改访问的服务器 2 的代码,添加对应的响应头,实现 CORS 跨域。
三、使用 postMessage 实现跨域
postMessage 是 H5 的新 API,跨文档消息传送(cross-document messaging),有时候简称为 XMD,指的是在来自不同域的页面间传递消息。
调用方式:window.postMessage(message, targetOrigin)
message:发送的数据
targetOrigin:发送的窗口的域
在对应的页面中用 message 事件接收,事件对象中有 data、origin、source 三个重要信息
data:接收到的数据
origin:接收到数据源的域(数据来自哪个域)
source:接收到数据源的窗口对象(数据来自哪个窗口对象)
使用场景:不是使用 Ajax 的数据通信,更多是在两个页面之间的通信,在 A 页面中引入 B 页面,在 A、B 两个页面之间通信。
与上面 CORS 类似,我们要创建跨域场景,搭建两个端口号不同的 Nodejs 服务器,后面相同方式就不多赘述了。
通过访问http://localhost:3000/a.html,在a.html 中使用 iframe 标签引入http://localhost:4000/b.html,在两个窗口间传递数据。
四、使用 window.name 实现跨域
同样是页面之间的通信,需要借助 iframe 标签,A 页面和 B 页面是同域的http://localhost:3000,C页面在独立的域http://localhost:4000。
实现思路:在 A 页面中将 iframe 的 src 指向 C 页面,在 C 页面中将属性值存入 window.name 中,再把 iframe 的 src 换成同域的 B 页面,在当前的 iframe 的 window 对象中取出 name 的值,访问http://localhost:3000/a.html。
五、使用 location.hash 实现跨域
与 window.name 跨域的情况相同,是不同域的页面间的参数传递,需要借助 iframe 标签,A 页面和 B 页面是同域的http://localhost:3000,C页面是独立的域http://localhost:4000。
实现思路:A 页面通过 iframe 引入 C 页面,并给 C 页面传一个 hash 值,C 页面收到 hash 值后创建 iframe 引入 B 页面,把 hash 值传给 B 页面,B 页面将自己的 hash 值放在 A 页面的 hash 值中,访问http://localhost:3000/a.html。
六、使用 document.domain 实现跨域
使用场景:不是万能的跨域方式,大多使用于同一公司不同产品间获取数据,必须是一级域名和二级域名的关系,如 www.baidu.com 与 video.baidu.com 之间。
想要模拟使用 document.domain 跨域的场景需要做些小小的准备,到 C:WindowsSystem32driversetc 该路径下找到 hosts 文件,在最下面创建一个一级域名和一个二级域名。
127.0.0.1 www.domainacross.com
127.0.0.1 sub.domainacross.com
命名是随意的,只要是符合一级域名与 二级域名的关系即可,然后访问http://www.domainacross.com:3000/a.html。
七、使用 WebSocket 实现跨域
WebSocket 没有跨域限制,高级 API(不兼容),想要兼容低版本浏览器,可以使用 socket.io 的库,WebSocket 与 HTTP 内部都是基于 TCP 协议,区别在于 HTTP 是单向的(单双工),WebSocket 是双向的(全双工),协议是 ws:// 和 wss:// 对应 http:// 和https://,因为没有跨域限制,所以使用file:// 协议也可以进行通信。
由于我们在 NodeJS 服务中使用了 WebSocket,所以需要安装对应的依赖:
npm install ws --save
八、使用 nginx 实现跨域
nginx 本身就是一个服务器,因此我们需要去 nginx 官网下载服务环境http://nginx.org/en/download....。
下载后解压到一个文件夹中
双击 nginx.exe 启动(此时可以通过http://localhost访问 nginx 服务)
在目录新建 json 文件夹
进入 json 文件夹新建 data.json 文件并写入内容
回到 nginx 根目录进入 conf 文件夹
使用编辑器打开 nginx.conf 进行配置
data.json 文件:
nginx.conf 文件:
含义:
~.*.json:代表忽略大小写,后缀名为 json 的文件;
root json:代表 json 文件夹;
add_header:代表加入跨域的响应头及允许访问的域,* 为允许任何访问。
在 nginx 根目录启动 cmd 命令行(windows 系统必须使用 cmd 命令行)执行下面代码重启 nginx。
nginx -s reload
不跨域访问:http://localhost/data.json
跨域访问时需要创建跨域条件代码如下:
跨域访问:http://localhost:3000/index.html
九、使用 http-proxy-middleware 实现跨域
NodeJS 中间件 http-proxy-middleware 实现跨域代理,原理大致与 nginx 相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置 cookieDomainRewrite 参数修改响应头中 cookie 中的域名,实现当前域的 cookie 写入,方便接口登录认证。
1、非 vue 框架的跨域(2 次跨域)
中间代理服务中使用了 http-proxy-middleware 中间件,因此需要提前下载:
npm install http-proxy-middleware --save-dev
vue 框架的跨域(1 次跨域)
利用 node + webpack + webpack-dev-server 代理接口跨域。在开发环境下,由于 Vue 渲染服务和接口代理服务都是 webpack-dev-server,所以页面与代理接口之间不再跨域,无须设置 Headers 跨域信息了。
本篇文章在于帮助我们理解跨域,以及不同跨域方式的基本原理,在公司的项目比较多,多个域使用同一个服务器或者数据,以及在开发环境时,跨域的情况基本无法避免,一般会有各种各样形式的跨域解决方案,但其根本原理基本都在上面的跨域方式当中方式,我们可以根据开发场景不同,选择最合适的跨域解决方案。
最后
“相信有很多想学前端的小伙伴,今年年初我花了一个月整理了一份最适合2018年学习的web前端干货,从最基础的HTML+CSS+JS到移动端HTML5等都有整理,送给每一位前端小伙伴,53763,1707这里是小白聚集地,欢迎初学和进阶中的小伙伴。”
祝大家早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。