ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于若依-Vue,接入静态HTML页面

基于若依-Vue,接入静态HTML页面 1.在前端文件夹中RuoYi-Vue/ruoyi-ui/public创建相应的包例如我在这就创建了一个monitor包并放入了相应的index.html模板如图所示2.在首页创建了一个按钮点击按钮触发goTarget方法跳转到相应页面3.index.vue页面主要用于在页面中嵌入一个iframe内嵌框架并通过postMessage实现父子页面间的安全通信传递身份认证 Token1. 组件功能概述核心作用嵌入一个监控页面/monitor/liujiaxia/index.html并将当前用户的认证 Token 从父页面传递到 iframe 子页面。安全机制通过localStoragepostMessage双保险传递 Token确保 iframe 内页面能获取到授权信息。template div classapp-container div classmonitor-container iframe refmonitorIframe src/monitor/liujiaxia/index.html frameborder0 width100% height100% styleborder: none; loadonIframeLoad /iframe /div /div /template script import { getToken } from /utils/auth export default { name: LiuJiaXiaMonitor, data() { return { token: null }; }, mounted() { this.token getToken(); // 進入iframe時將token寫入localStorage localStorage.setItem(iframe_token, this.token); // 監聽iframe的請求 window.addEventListener(message, this.onMessageFromIframe); }, beforeDestroy() { window.removeEventListener(message, this.onMessageFromIframe); }, methods: { onIframeLoad() { this.sendTokenToIframe(); }, onMessageFromIframe(event) { if (event.data event.data.type REQUEST_AUTH_TOKEN) { this.sendTokenToIframe(); } }, sendTokenToIframe() { const iframe this.$refs.monitorIframe; if (iframe iframe.contentWindow) { try { console.log([iframe] 發送REQUEST_AUTH_TOKEN給父頁面); iframe.contentWindow.postMessage({ type: AUTH_TOKEN, token: this.token }, *); } catch (error) { console.error(向iframe傳遞token失敗:, error); } } } } }; /script style scoped .monitor-container { width: 100%; height: calc(100vh - 120px); overflow: hidden; } iframe { width: 100%; height: 100%; border: none; } /style2. iframe 与父页面通过 postMessage 传递 Token 的安全注意事项在通过postMessage传递身份认证 Token 时安全性至关重要。如果处理不当Token 可能被恶意页面窃取导致用户身份被冒用。以下是几个必须注意的安全要点2.1 校验消息来源event.origin无论是父页面还是 iframe 子页面在接收message事件时都必须校验event.origin确保消息来自可信来源。否则任意第三方页面都可以向你的页面发送伪造消息诱导页面泄露 Token。父页面在接收 iframe 发来的消息时应校验event.origin是否为 iframe 的实际域名iframe 子页面在接收父页面消息时也应校验event.origin是否为父页面的域名。示例代码如下// 父页面接收消息时校验来源 window.addEventListener(message, function(event) { // 只接受来自 iframe 所在域名的消息 if (event.origin ! https://your-iframe-domain.com) { console.warn(收到来自未知来源的消息已忽略:, event.origin); return; } if (event.data event.data.type REQUEST_AUTH_TOKEN) { this.sendTokenToIframe(); } }); // iframe 子页面接收消息时校验来源 window.addEventListener(message, function(event) { // 只接受来自父页面域名的消息 if (event.origin ! https://your-parent-domain.com) { console.warn(收到来自未知来源的消息已忽略:, event.origin); return; } if (event.data event.data.type AUTH_TOKEN) { authToken event.data.token; localStorage.setItem(iframe_token, authToken); } });2.2 避免 Token 泄露Token 属于敏感信息在传递过程中应尽量避免以下风险不要将 Token 写入 URLURL 可能被浏览器历史、代理服务器或第三方插件记录导致 Token 泄露。避免在 localStorage 中长期保存 TokenlocalStorage 中的内容对同源下的所有脚本可见一旦页面被注入恶意脚本Token 就会被窃取。建议仅在 iframe 加载期间临时使用并在不需要时及时清理。不要将 Token 打印到控制台生产环境中应移除console.log等调试输出避免 Token 在开发者工具中暴露。使用 HTTPS确保父页面和 iframe 页面都通过 HTTPS 访问防止 Token 在传输过程中被中间人截获。2.3 使用*作为 targetOrigin 的风险与替代方案在调用postMessage时第二个参数targetOrigin用于指定消息的接收目标。如果写成*表示允许任何窗口接收该消息这会带来严重的安全风险Token 可能被任意页面接收如果 iframe 被恶意页面替换或嵌套使用*发送的 Token 会被恶意页面直接获取。消息可能被篡改攻击者可以伪造消息发送给父页面或 iframe诱导其执行非预期操作。正确的做法是明确指定 iframe 的实际来源地址例如// 不安全的写法任何页面都能收到 Token iframe.contentWindow.postMessage({ type: AUTH_TOKEN, token: this.token }, *); // 安全的写法只有指定来源的 iframe 才能收到 Token iframe.contentWindow.postMessage({ type: AUTH_TOKEN, token: this.token }, https://your-iframe-domain.com);同理iframe 子页面在向父页面发送消息时也应将targetOrigin指定为父页面的实际域名而不是使用*// 不安全的写法 window.parent.postMessage({ type: REQUEST_AUTH_TOKEN }, *); // 安全的写法 window.parent.postMessage({ type: REQUEST_AUTH_TOKEN }, https://your-parent-domain.com);如果父页面与 iframe 同源也可以直接使用window.location.origin作为targetOrigin既安全又便于维护。4.在HTML页面中插入一个折线图div stylewidth:100%;height:100%;margin-top:-40px; iddataChart dg-chart-theme{color:#faf7fa,actualBackgroundColor:#030303} !--最新200条数据折现图-- /div5.由于这是一个静态页面需要加入axios来请求后端的APIscript src/monitor/liujiaxia/js/jquery.min.js/script script src/monitor/liujiaxia/js/echarts.min.js/script script src/monitor/liujiaxia/js/fq.js/script script src/monitor/liujiaxia/js/axios.min.js/script script var lazyFun; var authToken localStorage.getItem(iframe_token); if (!authToken) { window.parent.postMessage({ type: REQUEST_AUTH_TOKEN }, *); } function init(el, width, height) { var _el document.getElementById(el); var hScale window.innerHeight / height; var wScale window.innerWidth / width; _el.style.transform scale( wScale , hScale ) } init(mainbody, 1920, 1080); window.onresize function() { clearTimeout(lazyFun); lazyFun setTimeout(function() { init(mainbody, 1920, 1080) }, 600); }; // 監聽來自父頁面的消息 window.addEventListener(message, function(event) { if (event.data event.data.type AUTH_TOKEN) { var wasTokenMissing !authToken; authToken event.data.token; localStorage.setItem(iframe_token, authToken); // 写入localStorage保存刷新後還在 if (wasTokenMissing) { fetchLatestData(); } } }); // 初始化数据图表 var dataChart echarts.init(document.getElementById(dataChart)); // function fetchLatestData() { if (!authToken) { console.error([iframe] 未獲取到認證token無法請求數據); return; } // 使用axios調用後端接口 axios.get(/dev-api/screen/list2, { headers: { Content-Type: application/json, Authorization: Bearer authToken } }) .then(function(response) { console.log(获取数据成功:, response.data.data); // 假設返回的數據格式為數組取最新200條 var data response.data.data ? response.data.data.slice(-200) : response.data.slice(-200); updateChart(data); }) .catch(function(error) { console.error(获取数据失敗:, error); // 接口失敗時直接報錯不顯示模擬數據 throw error; }); } // 更新圖表 function updateChart(data) { // 数据格式使用dateTime作为时间data1作为数值 var times data.map(function(item) { // 將ISO时间格式转换为更友好的显示格式 var date new Date(item.dateTime); return date.toLocaleTimeString(zh-CN, { hour: 2-digit, minute: 2-digit, second: 2-digit }); }); var values data.map(function(item) { return item.data1; }); var option { backgroundColor: transparent, title: { text: 最新数据监测, textStyle: { color: #faf7fa, fontSize: 14 }, left: center }, tooltip: { trigger: axis, backgroundColor: rgba(0,0,0,0.8), borderColor: #faf7fa, textStyle: { color: #faf7fa }, formatter: function(params) { var dataIndex params[0].dataIndex; var originalData data[dataIndex]; return 时间: originalData.dateTime br/ 数值: params[0].value; } }, grid: { left: 3%, right: 4%, bottom: 3%, containLabel: true }, xAxis: { type: category, data: times, axisLine: { lineStyle: { color: #faf7fa } }, axisLabel: { color: #faf7fa, fontSize: 10, rotate: 45 } }, yAxis: { type: value, axisLine: { lineStyle: { color: #faf7fa } }, axisLabel: { color: #faf7fa }, splitLine: { lineStyle: { color: rgba(250,247,250,0.2) } } }, series: [{ name: 检測值, type: line, data: values, smooth: true, lineStyle: { color: #00ff00, width: 2 }, itemStyle: { color: #00ff00 }, areaStyle: { color: { type: linear, x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: rgba(0,255,0,0.3) }, { offset: 1, color: rgba(0,255,0,0.1) }] } } }] }; dataChart.setOption(option); } // 頁面加載完成後初始化圖表 $(document).ready(function() { // 如果localStorage中已有token則立即獲取數據 if (authToken) { fetchLatestData(); } // 每30秒更新一次數據但需要檢查token setInterval(function() { if (authToken) { fetchLatestData(); } }, 30000); }); /script
返回列表