RuoYi-Cloud 免登录与页面内嵌实现
RuoYi-Cloud 免登录与页面内嵌实现文档概述本项目实现了两个核心功能RuoYi-Cloud 免登录SSO通过 Token 方式跳过登录界面自动获取系统访问凭证RuoYi-Vue 内嵌 RuoYi-Cloud 页面通过 iframe 将 RuoYi-Cloud 的视频直播页面嵌入到 RuoYi-Vue 系统中一、前端实现RuoYi-Cloud1.1 内嵌页面改造原始视频直播页面原始页面为完整的实时视频直播页包含设备树DeviceTree、EasyPlayer 播放器、分屏控制、云台操作等功能。template div classapp-container el-row :gutter20 splitpanes classdefault-theme pane size20 el-col DeviceTree clickEventclickEvent :isContextmenufalse/DeviceTree /el-col /pane pane size80 el-col div idlive classlive-container div v-loadingloading classlive-content element-loading-text拼命加载中 div classvideo-container div classcontrol-bar !-- 分屏选择、清空、保存布局、恢复布局、全屏等工具栏 -- /div div classplayer-container div refplayBox classplay-grid :styleliveStyle !-- EasyPlayer 播放器循环渲染 -- /div /div /div /div /div /el-col /pane /splitpanes /el-row /div /template新建内嵌专用页面改造要点外层增加.embed-page容器设置height: 100vh; width: 100%重置html, body, #app的margin: 0; padding: 0; height: 100%其余 DOM 结构、脚本逻辑、样式保持不变template div classembed-page div classapp-container el-row :gutter20 !-- 与原始页面完全相同的 splitpanes DeviceTree 播放器结构 -- /el-row /div /div /template style scoped langscss html, body, #app { margin: 0; padding: 0; height: 100%; } .embed-page { height: 100vh; width: 100%; } /* 其余样式与原始页面一致 */ /style1.2 登录页改造原始登录页完整的登录页面包含账号、密码、验证码输入框以及炫酷的科技感动画背景网格地面、扫描线、信号点、REC 录制指示、十字准星等。新建 SSO 专用登录页fpjklogin.vue核心思路保留原登录页的脚本逻辑和样式但将模板内容清空并新增getLoginByNameAndTokenJ()方法处理 SSO 登录。template div classlogin !-- 模板内容留空仅作占位 -- /div /template script setup langts 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 Promisevoid((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() /scriptSSO 登录流程从 URL?accessTokenxxx中读取加密的 Token解析 Token格式用户名$加密密码使用 AES 算法密钥secret_key_123解密密码调用后端ssologin接口换取系统 Token获取用户信息、生成动态路由跳转至/embed嵌入页面1.3 路由配置新建独立的embedRoutes数组与系统原有菜单路由隔离import{createWebHistory,createRouter}fromvue-routerimportLayoutfrom/layout/index.vueimportBlankLayoutfrom/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// 自定义标记用于权限判断}}]}]constroutercreateRouter({history:createWebHistory(),routes:[...constantRoutes,...embedRoutes],scrollBehavior(to,from,savedPosition){if(savedPosition)returnsavedPositionreturn{top:0}},})exportdefaultrouter关键点使用BlankLayout空白布局替代默认的Layout避免显示侧边栏、顶部导航等embedRoutes与constantRoutes合并加载1.4 用户 StoreSSO 登录接口封装在useUserStore中新增ssoLoginactionimport{login,logout,getInfo,ssologin}from/api/loginimport{getToken,setToken,removeToken}from/utils/authconstuseUserStoredefineStore(user,{state:():UserState({token:getToken(),id:,name:,nickName:,avatar:,roles:[],permissions:[]}),actions:{// 普通登录login(userInfo){returnnewPromisevoid((resolve,reject){login(userInfo.username,userInfo.password,userInfo.code,userInfo.uuid).then(res{setToken(res.data.access_token)this.tokenres.data.access_tokenresolve()}).catch(errorreject(error))})},// 新增SSO 免登录ssoLogin(loginInfo:{accessToken:string}){returnnewPromisevoid((resolve,reject){ssologin(loginInfo).then((res:any){constdatares.dataif(datadata.access_token){setToken(data.access_token)this.tokendata.access_tokenresolve()}else{consttokenres.token||data?.tokenif(token){setToken(token)this.tokentokenresolve()}else{reject(newError(SSO Login failed: No token received))}}}).catch((error:any)reject(error))})},// 获取用户信息getInfo(){// ... 原有逻辑},// 退出系统logOut(){// ... 原有逻辑}}})exportdefaultuseUserStore二、后端实现RuoYi-Cloud2.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/zip2.2 SSO 登录控制器在 AuthController 中新增ssologin接口PostMapping(login)publicR?login(RequestBodyLoginBodyform){// 用户登录LoginUseruserInfosysLoginService.login(form.getUsername(),form.getPassword());// 获取登录 tokenreturnR.ok(tokenService.createToken(userInfo));}/** * SSO 免登录接口 */PostMapping(ssologin)publicR?ssoLogin(ValidRequestBodySsoLoginDTOssoLoginDTO){// 调用 SSO 登录服务生成系统 tokenLoginUserloginUserssoLoginService.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[]tokenPartsaccessToken.split(\\$);StringusernametokenParts[0];// 提取用户名// 2. 查询用户信息RLoginUseruserResultremoteUserService.getUserInfo(username,SecurityConstants.INNER);if(!R.isSuccess(userResult)||userResult.getData()null){thrownewServiceException(未查询到该用户信息);}// 3. 返回用户信息由 Controller 生成 tokenreturnuserResult.getData();}}三、RuoYi-Vue 内嵌实现3.1 内嵌页面在 RuoYi-Vue 项目中新建页面通过 iframe 加载 RuoYi-Cloud 的 SSO 登录地址template div classapp-container stylepadding:15px iframe idtest :srcurl stylewidth: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?accessTokenadmin$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.javaPostMapping(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六、注意事项Token 安全性生产环境中accessToken应使用强加密算法如 AES-256并通过 HTTPS 传输Token 过期处理需要处理 SSO Token 过期后的重新登录逻辑跨域问题iframe 嵌入需确保 RuoYi-Cloud 配置了正确的 CORS 策略用户权限SSO 登录后仍需调用getInfo和generateRoutes确保权限路由正常加载密钥管理secret_key_123仅为演示密钥实际项目应使用配置文件或密钥管理服务七、扩展建议支持多种 Token 格式JWT、OAuth2 等增加 Token 签名验证机制防止伪造实现统一的 SSO 认证中心支持多系统单点登录优化 iframe 通信机制支持父子页面数据交互