目录
简介
Iframe(内联框架)是一种HTML元素,用于在网页上嵌入另一个HTML文档。然而,出于安全考虑,浏览器实施了同源策略,这意味着来自不同源的文档不能通过JavaScript互相交互。
实现iframe
<template>
<div>
<el-card class='container-card-second' v-loading='loading' element-loading-text='加载中...'>
<iframe
id='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页面内的子页面来使用了。