目录
简介
实现iframe
后端安全策略
通过Ngnix代理实现SAMEORIGIN
iframe的事件拦截,自定义处理
iframe的状态保持(解决vue中iframe重载)
解决方法
简介
Iframe(内联框架)是一种HTML元素,用于在网页上嵌入另一个HTML文档。然而,出于安全考虑,浏览器实施了同源策略,这意味着来自不同源的文档不能通过JavaScript互相交互。
实现iframe
<template><div><el-card class='container-card-second' v-loading='loading' element-loading-text='加载中...'><iframeid='iframeId'ref='myIframe'@load='handleIframeLoad'class='iframe-container':src='url'width='100%'frameborder='0'allowfullscreen></iframe></el-card></div>
</template>
然后可以通过响应式完成url的切换
mounted() {this.url = "http://localhost:8080";}
后端安全策略
在Django/settings.py中有相关iframe的安全设置
# 定义iframe允许跨域请求的域名列表
# X_FRAME_OPTIONS = 'ALLOW-FROM http://localhost:8080/ http://192.168.252.227:8081/'# X_FRAME_OPTIONS = 'ALLOW-FROM http://192.168.252.227:8081'
X_FRAME_OPTIONS = 'SAMEORIGIN'
Django的
X_FRAME_OPTIONS
配置项用于设置HTTP响应头X-Frame-Options
,这有助于防止点击劫持攻击。以下是X_FRAME_OPTIONS
的作用和案例:
作用:
X-Frame-Options
是一个HTTP响应头,用来指示浏览器是否允许页面在<frame>
、<iframe>
、<embed>
或<object>
中被展示。通过设置这个头,网站可以确保自己的内容不被嵌入到其他网站中,从而避免点击劫持攻击。1默认设置:Django默认启用了
XFrameOptionsMiddleware
中间件,该中间件会为每个响应设置X-Frame-Options
为DENY
,意味着页面不能在任何框架中被展示。1全局设置:可以通过在Django的设置文件中设置
X_FRAME_OPTIONS
变量来改变全局的X-Frame-Options
值。例如,将其设置为SAMEORIGIN
,允许页面在相同源的框架中被展示。局部设置:通过
ALLOW-FROM
可设置自定义的网址,但是没有SAMEORIGIN
好用。
ALLOW-FROM
和SAMEORIGIN
是X-Frame-Options
HTTP响应头的两个不同的指令,它们控制页面是否可以在<frame>
、<iframe>
、<embed>
或<object>
中被展示,但它们的应用范围和安全性有所不同:
SAMEORIGIN:
SAMEORIGIN
指令告诉浏览器只允许来自同一个源(即相同的协议、域名和端口)的页面在框架中展示当前页面。这个指令提供了一定程度的安全性,因为它限制了页面只能在相同源的上下文中被加载,从而避免了跨站点的点击劫持攻击。
然而,如果网站有多个子域,并且需要在这些子域之间共享内容,
SAMEORIGIN
可能不够灵活。ALLOW-FROM(注意:并非所有浏览器都支持):
ALLOW-FROM
指令允许指定页面可以在来自特定来源的框架中展示。这需要指定一个具体的URL。例如,如果设置为
ALLOW-FROM https://example.com
,那么只有来自https://example.com
的页面才能在框架中展示当前页面。这个指令提供了更细粒度的控制,允许网站与特定的合作伙伴或服务共享内容,同时仍然保持较高的安全性。
区别:
SAMEORIGIN
是一个更保守的策略,适用于大多数情况,因为它只允许相同源的页面加载。
ALLOW-FROM
提供了更细粒度的控制,但需要谨慎使用,因为它可能会引入安全风险,特别是如果允许的来源不够安全或被恶意利用。请注意,
ALLOW-FROM
并不是所有浏览器都支持,而且它的使用可能会更加复杂,因为需要正确地指定允许的来源。相比之下,SAMEORIGIN
是一个更广泛支持且更安全的选项。在实际使用中,应根据具体需求和安全考虑来选择合适的指令。
通过Ngnix代理实现SAMEORIGIN
首先vue开发的前端一般都会使用ngnix代理,那么只需要新增一个在此端口下新增一个path用于代理你的iframe的内部页面。
例如
server {listen 8081;server_name localhost;location /api {#需要代理访问的后端服务器地址proxy_pass http://localhost:8000;#重写以/api为baseURL的接口地址rewrite "^/api/(.*)$" /$1 break;}}
根据上述配置:
如果你将
iframe
内容通过 Nginx 代理设置在8081/api
,并且你的前端页面也是通过8081
端口提供服务的,那么即使实际内容来自于后端服务(假设是8000
端口),从浏览器的角度来看,这些请求都是发往同一个源(即localhost:8081
)。在这种情况下,当浏览器对
iframe
请求执行同源策略检查时,它会认为所有请求都是从同一个源发起的,因为它们都有相同的协议(例如http
)、域名(例如localhost
)和端口号(8081
)。所以,在这种配置下,就算实际上iframe
的内容是由不同端口的后端服务生成的,由于浏览器只能"看到"8081
端口,X-Frame-Options
设为SAMEORIGIN
是可以工作的,因为它满足了从同一来源加载iframe
内容的要求Nginx首先将请求转发到
http://localhost:8000
。接着,
rewrite
指令将请求的URI从/api/users
重写为/users
。最终,后端服务将收到不带有
/api
前缀的请求路径,即/users
。那么你就可以通过
http://
来访问你的iframe网页你的项目ip:8081/api
至此若你的iframe是内部网页则可以通过此方案实现SAMEORIGIN
iframe的事件拦截,自定义处理
使用@load='handleIframeLoad'和ref='myIframe'
addEvent(iframe) {iframe.contentWindow.document.addEventListener('click', event => {// 获取超链接的href属性const href = event.target.href;// 定义你希望拦截并使用Vue路由处理的URL部分const mark_val = '/xxx_edit';// 如果href包含mark_val,则进行Vue路由导航if (href.includes(mark_val)) {// 阻止默认行为event.preventDefault();// 提取ID或其他必要的路由参数const id = href.split('/').pop();// 使用Vue路由进行导航this.$router.push(`${mark_val}/${id}`);}// 若不符合特定条件,则不阻止默认行为,链接将按原本方式跳转});},handleIframeLoad() {const iframe = this.$refs.myIframe;if (iframe) {this.addEvent(iframe);}setTimeout(() => {this.loading = false;}, 1500);setTimeout(() => {this.addEvent(iframe);}, 3000);}
笔者增加延时是为了防止页面加载缓慢,没有成功绑定到事件。这里演示了拦截点击事件然后做处理。
iframe的状态保持(解决vue中iframe重载)
上述步骤都完成后,你可能会发现你的页面在子页面切换的时候总是会重载iframe的url。
1.Vue 的缓存机制并不是直接存储 DOM 结构,而是将 DOM 节点抽象成了一个个 VNode节点。因此,Vue 的 keep-alive 缓存也是基于 VNode节点 而不是直接存储 DOM 节点。在需要渲染的时候从Vnode渲染到真实DOM上。
2.iframe中keep-alive机制失效原因:iframe页里的内容并不属于节点的信息,所以使用keep-alive依然会重新渲染iframe内的内容。而且iframe每一次渲染就相当于打开一个新的网页窗口,即使把节点保存下来,在渲染时iframe页还是刷新的
解决方法
找到项目的路由入口,然后针对iframe页面做非keep-alive的处理。
以下为实例代码
<div class='content'><transition name='move' mode='out-in'><!-- isRouterAlive && !$route.meta.iframe 排除iframe页面的显示由下面的独立的iframe页面实现显示--><keep-alive :include='tagsList' :max='15'><router-view:key='$route.fullPath'ref='routerView'v-if='isRouterAlive && !$route.meta.iframe'/></keep-alive></transition><!-- 独立的iframe页面;根据路由的fullPath(包括路由的参数 方便处理不同参数的同一个组件)判断是否显示--><component class='no-iframe-page'v-for='item in hasOpenComponentsArr':key='item.fullPath':is='item.name'v-show='$route.fullPath === item.path'></component></div>
其中逻辑部分为
computed: {comTags() {return this.$store.state.globalTagsList;},/*** 利用全局响应式的标签列表过滤iframe页面,并且可以实现动态打开iframe页面* @returns*/hasOpenComponentsArr() {return this.comTags.filter(item => item.meta && item.meta.iframe);}}
globalTagsList是利用vuex(此处的使用方式参考点我查看)将标签页做了全局状态管理。
例如
tagsList: {get() {return this.$store.state.globalTagsList;},set(value) {this.$store.commit('SET_GLOBAL_TAGS_LIST', value);}}
笔者通过路由的元信息来实现iframe页面的区分,以下为路由部分的代码实例
{path: '/xxx/:id',component: resolve => require(['../views/xxx/xxx.vue'], resolve),meta: { title: 'xxx', iframe: true },name: 'xxx'}]
至此iframe页面可以完全作为整个web页面内的子页面来使用了。