当前位置: 首页 > news >正文

RuoYi-Cloud 免登录与页面内嵌实现

RuoYi-Cloud 免登录与页面内嵌实现文档

概述

本项目实现了两个核心功能:

  1. RuoYi-Cloud 免登录(SSO):通过 Token 方式跳过登录界面,自动获取系统访问凭证
  2. RuoYi-Vue 内嵌 RuoYi-Cloud 页面:通过 iframe 将 RuoYi-Cloud 的视频直播页面嵌入到 RuoYi-Vue 系统中

一、前端实现(RuoYi-Cloud)

1.1 内嵌页面改造

原始视频直播页面

原始页面为完整的实时视频直播页,包含设备树(DeviceTree)、EasyPlayer 播放器、分屏控制、云台操作等功能。

<template> <div class="app-container"> <el-row :gutter="20"> <splitpanes class="default-theme"> <pane size="20"> <el-col> <DeviceTree @clickEvent="clickEvent" :isContextmenu="false"></DeviceTree> </el-col> </pane> <pane size="80"> <el-col> <div id="live" class="live-container"> <div v-loading="loading" class="live-content" element-loading-text="拼命加载中"> <div class="video-container"> <div class="control-bar"> <!-- 分屏选择、清空、保存布局、恢复布局、全屏等工具栏 --> </div> <div class="player-container"> <div ref="playBox" class="play-grid" :style="liveStyle"> <!-- EasyPlayer 播放器循环渲染 --> </div> </div> </div> </div> </div> </el-col> </pane> </splitpanes> </el-row> </div> </template>
新建内嵌专用页面

改造要点

  • 外层增加.embed-page容器,设置height: 100vh; width: 100%
  • 重置html, body, #appmargin: 0; padding: 0; height: 100%
  • 其余 DOM 结构、脚本逻辑、样式保持不变
<template> <div class="embed-page"> <div class="app-container"> <el-row :gutter="20"> <!-- 与原始页面完全相同的 splitpanes + DeviceTree + 播放器结构 --> </el-row> </div> </div> </template> <style scoped lang="scss"> html, body, #app { margin: 0; padding: 0; height: 100%; } .embed-page { height: 100vh; width: 100%; } /* 其余样式与原始页面一致 */ </style>

1.2 登录页改造

原始登录页

完整的登录页面,包含账号、密码、验证码输入框,以及炫酷的科技感动画背景(网格地面、扫描线、信号点、REC 录制指示、十字准星等)。

新建 SSO 专用登录页(fpjklogin.vue)

核心思路:保留原登录页的脚本逻辑和样式,但将模板内容清空,并新增getLoginByNameAndTokenJ()方法处理 SSO 登录。

<template> <div class="login"> <!-- 模板内容留空,仅作占位 --> </div> </template> <script setup lang="ts"> import { getCodeImg } from "@/api/login" import Cookies from "js-cookie" import { encrypt, decrypt } from "@/utils/jsencrypt" import useUserStore from '@/store/modules/user' import usePermissionStore from '@/store/modules/permission' import { isHttp } from '@/utils/validate' import CryptoJS from 'crypto-js' const userStore = useUserStore() const permissionStore = usePermissionStore() const route = useRoute() const router = useRouter() /** * SSO 单点登录核心方法 * 从 URL 中读取 accessToken 参数,格式:用户名$加密后的密码 */ function getLoginByNameAndTokenJ(): void { // 1. 获取地址栏中的 token const accessToken = route.query.accessToken as string // 2. 校验 token 是否存在 if (!accessToken) { // 没有 token,显示普通登录表单 return } // 3. 开始处理 SSO 登录 loading.value = true try { // 4. 解析 token 并解密密码 const parts = accessToken.split("$") if (parts.length < 2) { throw new Error("AccessToken 格式错误") } const passwordPart = parts[1].replace(/ /g, '+') // 替换空格为 + // 使用 AES 解密 let bytes = CryptoJS.AES.decrypt(passwordPart, 'secret_key_123') let decryptedPassword = bytes.toString(CryptoJS.enc.Utf8) // 构造登录信息 const logininfo = { accessToken: parts[0] + "$" + decryptedPassword } // 5. 调用 SSO 登录接口 userStore.ssoLogin(logininfo) .then(() => { // 6. 获取用户信息 return userStore.getInfo() }) .then(() => { // 7. 生成动态路由 return usePermissionStore().generateRoutes() }) .then((accessRoutes: any[]) => { // 8. 添加动态路由 accessRoutes.forEach((route: any) => { if (!isHttp(route.path) && !router.hasRoute(route.name)) { router.addRoute(route) } }) // 等待 addRoute 完成 return new Promise<void>((resolve) => { setTimeout(resolve, 100) }) }) .then(() => { // 9. 跳转至内嵌页面 loading.value = false router.push({ path: "/embed" }) }) .catch((err: any) => { console.error("SSO Login Error", err) loading.value = false }) } catch (error) { console.error("SSO Decryption Error", error) loading.value = false } } // 页面加载时执行 getCode() getCookie() getLoginByNameAndTokenJ() </script>

SSO 登录流程

  1. 从 URL?accessToken=xxx中读取加密的 Token
  2. 解析 Token(格式:用户名$加密密码
  3. 使用 AES 算法(密钥secret_key_123)解密密码
  4. 调用后端ssologin接口换取系统 Token
  5. 获取用户信息、生成动态路由
  6. 跳转至/embed嵌入页面

1.3 路由配置

新建独立的embedRoutes数组,与系统原有菜单路由隔离:

import{createWebHistory,createRouter}from'vue-router'importLayoutfrom'@/layout/index.vue'importBlankLayoutfrom'@/layout/embed/BlankLayout.vue'// 公共路由exportconstconstantRoutes=[// ... 原有路由{path:'/login',component:()=>import('@/views/login.vue'),hidden:true},{path:'/fpjklogin',// 新增 SSO 登录入口component:()=>import('@/views/fpjklogin.vue'),hidden:true},// ... 其他路由]// 外部嵌入专用路由,独立数组exportconstembedRoutes=[{path:'/embed',component:BlankLayout,// 空白布局,不走默认 layoutredirect:'/embed/demo',children:[{path:'demo',name:'EmbedDemo',component:()=>import('@/views/qs/embed/demo.vue'),meta:{title:'外部嵌入页面',hidden:true,isEmbed:true// 自定义标记,用于权限判断}}]}]constrouter=createRouter({history:createWebHistory(),routes:[...constantRoutes,...embedRoutes],scrollBehavior(to,from,savedPosition){if(savedPosition)returnsavedPositionreturn{top:0}},})exportdefaultrouter

关键点

  • 使用BlankLayout(空白布局)替代默认的Layout,避免显示侧边栏、顶部导航等
  • embedRoutesconstantRoutes合并加载

1.4 用户 Store(SSO 登录接口封装)

useUserStore中新增ssoLoginaction:

import{login,logout,getInfo,ssologin}from'@/api/login'import{getToken,setToken,removeToken}from'@/utils/auth'constuseUserStore=defineStore('user',{state:():UserState=>({token:getToken(),id:'',name:'',nickName:'',avatar:'',roles:[],permissions:[]}),actions:{// 普通登录login(userInfo){returnnewPromise<void>((resolve,reject)=>{login(userInfo.username,userInfo.password,userInfo.code,userInfo.uuid).then(res=>{setToken(res.data.access_token)this.token=res.data.access_tokenresolve()}).catch(error=>reject(error))})},// 新增:SSO 免登录ssoLogin(loginInfo:{accessToken:string}){returnnewPromise<void>((resolve,reject)=>{ssologin(loginInfo).then((res:any)=>{constdata=res.dataif(data&&data.access_token){setToken(data.access_token)this.token=data.access_tokenresolve()}else{consttoken=res.token||data?.tokenif(token){setToken(token)this.token=tokenresolve()}else{reject(newError('SSO Login failed: No token received'))}}}).catch((error:any)=>reject(error))})},// 获取用户信息getInfo(){// ... 原有逻辑},// 退出系统logOut(){// ... 原有逻辑}}})exportdefaultuseUserStore

二、后端实现(RuoYi-Cloud)

2.1 网关白名单配置

在 Nacos 的ruoyi-gateway-dev.yml中新增 SSO 接口白名单:

# 不校验白名单ignore:whites:-/auth/logout-/auth/login-/auth/ssologin# 新增 SSO 免登接口放行-/auth/register-/*/v2/api-docs-/*/v3/api-docs-/csrf-/zlm/index/hook/**-/zlm/cloudRecord/download/zip

2.2 SSO 登录控制器

在 AuthController 中新增ssologin接口:

@PostMapping("login")publicR<?>login(@RequestBodyLoginBodyform){// 用户登录LoginUseruserInfo=sysLoginService.login(form.getUsername(),form.getPassword());// 获取登录 tokenreturnR.ok(tokenService.createToken(userInfo));}/** * SSO 免登录接口 */@PostMapping("ssologin")publicR<?>ssoLogin(@Valid@RequestBodySsoLoginDTOssoLoginDTO){// 调用 SSO 登录服务,生成系统 tokenLoginUserloginUser=ssoLoginService.ssoLogin(ssoLoginDTO.getAccessToken());returnR.ok(tokenService.createToken(loginUser));}

2.3 SSO 登录服务

创建SsoLoginService处理 SSO 登录逻辑:

packagecom.ruoyi.auth.service;importcom.ruoyi.common.core.constant.SecurityConstants;importcom.ruoyi.common.core.domain.R;importcom.ruoyi.common.core.exception.ServiceException;importcom.ruoyi.common.security.service.TokenService;importcom.ruoyi.system.api.RemoteUserService;importcom.ruoyi.system.api.domain.SysUser;importcom.ruoyi.system.api.model.LoginUser;importjakarta.annotation.Resource;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.util.Map;@ServicepublicclassSsoLoginService{@ResourceprivateTokenServicetokenService;@AutowiredprivateRemoteUserServiceremoteUserService;/** * SSO 登录核心逻辑 * @param accessToken 前端解密后的 token(格式:用户名$密码/凭证) * @return 系统内有效的 access_token */publicLoginUserssoLogin(StringaccessToken){// 1. 解析 accessToken(与前端约定格式:用户标识$凭证)String[]tokenParts=accessToken.split("\\$");Stringusername=tokenParts[0];// 提取用户名// 2. 查询用户信息R<LoginUser>userResult=remoteUserService.getUserInfo(username,SecurityConstants.INNER);if(!R.isSuccess(userResult)||userResult.getData()==null){thrownewServiceException("未查询到该用户信息");}// 3. 返回用户信息(由 Controller 生成 token)returnuserResult.getData();}}

三、RuoYi-Vue 内嵌实现

3.1 内嵌页面

在 RuoYi-Vue 项目中新建页面,通过 iframe 加载 RuoYi-Cloud 的 SSO 登录地址:

<template> <div class="app-container" style="padding:15px"> <iframe id="test" :src="url" style="width:100%; height:800px; overflow:auto;" ></iframe> </div> </template> <script> import { getToken } from '@/utils/auth' export default { name: "Fpjk", data() { return { url: "" } }, created() { // 拼接 RuoYi-Cloud 的 SSO 登录地址 // 格式:用户名$密码(密码已在前端通过 AES 加密) this.url = 'http://localhost:83/fpjklogin?accessToken=admin$admin123' }, mounted() {} } </script>

关键点

  • iframe 的src指向 RuoYi-Cloud 的/fpjklogin页面
  • URL 参数accessToken携带用户名和加密后的密码
  • 实际生产环境中,密码应通过 AES 加密后再传递

四、整体实现流程

┌─────────────────────────────────────────────────────────────┐ │ 1. RuoYi-Vue 系统加载内嵌页面 │ │ ↓ │ │ 2. iframe 加载 http://localhost:83/fpjklogin?accessToken=...│ │ ↓ │ │ 3. RuoYi-Cloud 的 fpjklogin 页面解析 accessToken │ │ ↓ │ │ 4. 前端使用 AES 解密密码(密钥:secret_key_123) │ │ ↓ │ │ 5. 调用后端 /auth/ssologin 接口 │ │ ↓ │ │ 6. 后端 SsoLoginService 根据用户名查询用户信息 │ │ ↓ │ │ 7. 后端生成系统 access_token 并返回 │ │ ↓ │ │ 8. 前端保存 token,调用 getInfo 获取用户信息 │ │ ↓ │ │ 9. 生成动态路由,跳转至 /embed/demo 内嵌页面 │ │ ↓ │ │ 10. iframe 内显示完整的视频直播页面 │ └─────────────────────────────────────────────────────────────┘

五、关键配置总结

配置项位置说明
SSO 登录页RuoYi-Cloud/.../views/fpjklogin.vue处理 Token 解析和登录逻辑
内嵌直播页RuoYi-Cloud/.../views/qs/embed/demo.vue包装.embed-page容器
嵌入路由RuoYi-Cloud/.../router/index.jsembedRoutes独立数组
空白布局RuoYi-Cloud/.../layout/embed/BlankLayout.vue不显示侧边栏和导航
SSO 接口RuoYi-Cloud/.../controller/AuthController.java@PostMapping("ssologin")
SSO 服务RuoYi-Cloud/.../service/SsoLoginService.java根据用户名查询用户信息
网关白名单Nacosruoyi-gateway-dev.yml/auth/ssologin放行
User StoreRuoYi-Cloud/.../store/modules/user.js新增ssoLoginaction
内嵌入口RuoYi-Vue/.../views/.../Fpjk.vueiframe 加载 RuoYi-Cloud

六、注意事项

  1. Token 安全性:生产环境中,accessToken应使用强加密算法(如 AES-256),并通过 HTTPS 传输
  2. Token 过期处理:需要处理 SSO Token 过期后的重新登录逻辑
  3. 跨域问题:iframe 嵌入需确保 RuoYi-Cloud 配置了正确的 CORS 策略
  4. 用户权限:SSO 登录后仍需调用getInfogenerateRoutes确保权限路由正常加载
  5. 密钥管理secret_key_123仅为演示密钥,实际项目应使用配置文件或密钥管理服务

七、扩展建议

  • 支持多种 Token 格式(JWT、OAuth2 等)
  • 增加 Token 签名验证机制,防止伪造
  • 实现统一的 SSO 认证中心,支持多系统单点登录
  • 优化 iframe 通信机制,支持父子页面数据交互
http://www.gsyq.cn/news/1618406.html

相关文章:

  • 操作系统复习(二)
  • 机器视觉自动曝光综述
  • Ubuntu 18.04 上 ROS1 Melodic 安装配置教程
  • 机器学习模型生产部署:从PyTorch到K8s+Triton的工程实践
  • 彻底解决ChatGPT幻觉问题!2026大模型虚假信息规避实战方案
  • 元学习对话系统:少样本个性化适配的工业级实践
  • ORB-SLAM3 关键帧相似度计算
  • MySQL 迁移实战——如何实现真正的“零改造“平滑切换
  • 耐压仪一开机5kV,屏幕数据直接跳飞?换过三个牌子才找到答案
  • 2026长春靠谱人造草坪供应商,选这家不踩坑
  • Java毕设项目:基于 SpringBoot 的宠物诊疗设备调度管理系统的设计与实现 基于 SpringBoot 的宠物疫苗信息公示与统计系统的设计与实现 (源码+文档,讲解、调试运行,定制等)
  • 3分钟免费解锁QQ音乐格式限制:QMCFLAC2MP3让你的音乐真正自由播放
  • 2026 GitHub最受欢迎的10个AI开源项目盘点
  • cw-omnibus:一本 Android 开发书的全部示例代码
  • Codex 新手优选的 6 个实用 Skill:让 AI 真正成为你的开发助手
  • 鼠标革命:让你的普通鼠标在Mac上比触控板更好用!
  • 运行codex时出现登录失败:failed to start login server: 以一种访问权限不允许的方式做了一个访问套接字的尝试。 (os error 10013) 解决方案
  • 笔试强训 Day 19:小易的升级之路、礼物的最大价值、对称之美
  • Java毕设选题推荐:基于 Java 的学术资料智能检索管理系统的设计与实现 基于 Java 的文献资源分类统计管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 内网渗透测试实战指南:从信息收集到域控攻防的完整攻击链
  • 节点】[SmoothStep节点]原理解析与实际应用
  • 2026年AIGC检测怎么过?5大检测平台对比+AI痕迹降低实战指南
  • ZXing:一个扫描条码的基础库
  • ICM-42688-P与PIC18F4553在机器人控制与工业监测中的应用
  • 类比StandardServer, 抓住StandardService整体类依赖结构来理解
  • 【节点】[Clamp节点]原理解析与实际应用
  • Kubernetes 核心机制与运维实践知识精要
  • ROS2基本操作指令:从“节点”到“机器人”的完整工具箱
  • 重庆市二手房价格数据分析与可视化系统
  • 墨香情手游官方下载:2026 国风武侠手游优选正版高速官方下载通道