知识扫盲
跨域
-
跨域是因为 “同源策略”的限制,“同源”一般是指: 协议、域名(主域名以及子域名)和端口号三者相同,三者中的任何一个不相同都算作是跨域,不同域之间的相互请求资源就算作是跨域,这样都是会被浏览器限制的。
-
同源策略是由浏览器的安全限制导致的,浏览器之所以设置同源策略的限制主要是为了避免浏览器受到xss、CSFR等的攻击。
-
同源策略限制的内容有: Cookie、LocalStorage、IndexdDB等存储数据。Dom节点,AJAX请求发送后,被浏览器的拦截。(仅适用于)
-
img标签中的src、link中的href以及script中的src是允许跨域加载资源的。
-
跨域并不是请求发布出去,请求是能发出去的,服务器是能够收到请求并且正常的返回结果的,只是结果被浏览器给拦截了。
窗口
浏览器对象模型(Browser Object Model (BOM))允许JavaScript 与浏览器对话。窗口(Window 对象)也是浏览器对象模中的一个对象。所有浏览器都支持 window 对象。它代表浏览器的窗口。 所有全局 JavaScript 对象、函数、变量自动成为 window 对象的成员。
-
全局变量是 window 对象的属性。
-
全局函数是 window 对象的方法。
-
document 对象也是 window 对象属性。
window.document.getElementById("header");
document.getElementById("header");
上述的两行代码是等价的。
//窗口之间的消息发送
window.postMessage()
//打开新窗口
window.open()
//关闭当前窗口
window.close()
//移动当前窗口
window.moveTo()
//重新调整当前窗口
window.resizeTo()
窗口与窗口之间是绝对独立的,一个iframe就是一个窗口。
背景
一般我们在系统开发集成过程中经常会遇到跨域问题。什么时候回出现跨域问题,比如我们在做系统集成是,遇到以下案例场景(这也是真实的案例场景)。
- 项目A通过iframe内嵌项目B,A调用B中的一个对话框事件,打开B的一个新增数据窗口,但是窗口需要居中,蒙板需要全屏,所以B中的弹窗事件需要在A中执行涉及B跨域调用A的对话框工具类。
- B中弹出对话框关闭后需要回调父亲窗口的函数刷新数据
- 整个过程中反复切换iframe,另外弹窗也是iframe。过程反复绕了好几层。总结后需要分析下整个功能过程。
按照图中的过程其实没有跨域,因为所有操作没有涉及的不同域之间的资源访问。这里注意一下,通过构建iframe的方式集成不同域的应用系统本身是不会有跨域问题的(这里类似通过window.open打开新窗口,iframe方式也是一个新窗口)。窗口与窗口之间是完全独立的,但是如果涉及到不同域窗口之间的资源访问就会产生跨域问题。如下图:
为什么一定要在B域中访问A域中的对话框工具然后打开窗口。好吧,想象下其实这样的场景非常常见,只是大部分都是不同iframe窗口同域操作,所以我们没有遇到过跨域问题。当然原因其实就是对话框蒙板的体验问题,通过顶层窗口蒙板的效果会更好,用户体验更佳。
当然本案例中还涉及到B站点保存后需要回调A站点的回调函数
如果是同域的场景下,A站点的对话框工具类打开B站点的对话框页面过程中,就可以把A的回调函数赋值给B站点的对话框窗口页面的window对象,然后对话款中的保存函数执行完后直接调用回调函数。这也是大部分对话框组件的参数传递、回调机制的原理如下图
但是如果不是同域的,上诉所有场景,参数传递、函数的回调、包括对话框窗口的打开过程都会有跨域问题。因为按照同源策略,这里已经涉及到了不同域的资源访问。
解决方案
jsonp原理
利用script标签没有跨域限制的漏洞,网页可以得到从其他源动态产生的json数据。 JSONP请求一定需要对方的服务器做支持才可以;JSONP主要是通过声明一个回调函数,然后利用script的src属性后边跟上 ?callback=声明的回调函数,来进行数据处理,使用起来非常的简单和方便,而且可用于解决主流浏览器的跨域数据访问问题;但是,它仅仅只是支持get方法,具有一定的局限性,而且不安全,容易受到xss攻击;
cors
cors需要浏览器和后端同时支持。IE8和IE9需要通过XDomainReauest来进行实现;其实浏览器本身是会自动的进行cors通信的,因此,实现cors通信的关键是后端,后端需要对请求头的配置,也就是 Access-Control-Allow-Origin的配置,只要后端实现了cors,那么跨域的问题就解决了;
注意上述说明,cors主要是后端,这种场景在前后端分离的架构中经常会遇到。
spring mvc
一般主流的web框架都会有cors跨域策略配置,如下图spring mvc中的WebMvcConfigurer就有默认的cors扩展接口。
spring security
目前主流的安全框架如spring security也有关于cors的扩展配置接口。
自定义全局filter
当然我们也可以通过全局的自定义过滤器来实现跨域访问(其实不论是mvc还是security都是基于servlet的封装而框架,所以他们内部也是通过过滤器拦截请求并在请求头中添加跨域访问标识让浏览器放过跨域的后端资源访问)
本案例中因为涉及内容全是前端的资源调用,并不经过后端,所以这里就算后端通过filter实现跨域通信也是不管用的。
nginx反向代理
nginx反向代理跨域的实现原理主要是利用了服务器之间不存在跨域来进行实现的,类似于我们构建的node 本地服务器进行跨域。
nginx反向代理的实现是通过nginx 配置一个代理服务器(域名与domain1相同,端口不同)作为跳板机,反向代理访问domain2的接口,并且可以顺便修改cookie中的domain信息,便于当前域的写入,进而来实现跨域;
当前主流的前后端分离架构中最为主流的解决跨域问题得方案。
node 服务器作为中间代理进行跨域
同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略;前后端分离架构模式下,平时的研发环境中,其实就是该种模式,通过node实现中间代理。其实类似nginx,只是代理框架不一样。
postMessage
postMessage是 HTML5 XMLHttpRequest Level2 中的api,而且是为数不多可以跨域操作的 window 属性之一,它可用于解决以下方面的问题。
- 页面和其他打开的新窗口的数据传递;
- 多窗口之间的消息传递;
- 页面与嵌入的iframe消息传递;
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递;
本案例中其实就是采用了postMessage的方式,相比其他方案会存在一定的代码量,但是风险低,以上方案中cors在本案例中无法解决(原因是本案例中不涉及后端跨域),nginx代理中涉及到架构以及代码规范(因为当前项目中存在部分写死的uri,尤其是contextPath,存在一定风险)
解决过程
整个过程设计的代码量其实很少,原先的代码框架不变,比如打开对话框,保存等核心代码并不修改,只是在打开对话框、回调的时候通过消息来实现。
-
父窗口往子窗口发送消息
-
子窗口往父窗口发送消息
总结
- 本次跨域问题不涉及后端,纯粹是前端跨域问题。
- 其二其涉及的技术点并不复杂。
- 主要是业务逻辑+知识点的综合运用。
- 如何通过问题深化基础概念,基础理论知识,完善自身知识广度以及深度是我们需要重点学习的。
- 每一个问题都有它存在的价值,对于我们来说问题核心价值在于总结问题,不断完善自身知识点,最终在某一个方便形成体系,只有通过体系化的理论知识才能有效合理的处理工作中的日常问题。