# 大屏 — 免密登录 — 技术方案 > 供 `ruoyi-screen` 等大屏前端**自动登录**:无需账号密码与验证码,通过 RSA 加密 UUID 换取若依 JWT,再访问各 `bigScreen:*` 看板接口。 --- ## 1. 技术架构 | 项 | 说明 | | --- | --- | | **后端** | RuoYi **v3.9.2**(springboot2):JDK 8、Spring MVC | | **代码包** | `com.ruoyi.web.modules.screen`(`ScreenLoginController` / `ScreenLoginService` / `ScreenLoginRsaSupport` / `ScreenLoginProperties`) | | **鉴权** | 登录接口 `@Anonymous` 免鉴权;登录成功后与普通若依接口一致,请求头 `Authorization: Bearer ` | | **专用账号** | `sys_user` 预置用户(默认 `screenviewer`);校验存在且未删除、未停用 | | **菜单权限** | 登录成功后在 JWT 中注入 `bigScreen:*:*`,前端默认拥有全部大屏菜单与看板接口权限,**无需**在若依中单独授权 | | **加密** | `RSA/ECB/PKCS1Padding`,与若依后台 `jsencrypt` 密钥格式兼容 | | **客户端 IP** | **不校验**白名单/黑名单;仅写入登录日志 | **流程** ```mermaid sequenceDiagram participant FE as ruoyi-screen participant API as baqing-admin FE->>API: GET /bigScreen/publicKey API-->>FE: data.publicKey FE->>FE: 生成 UUID,JSEncrypt 公钥加密 FE->>API: POST /bigScreen/login(X-Screen-Token 或 body.token) API->>API: 私钥解密,校验 UUID 格式 API-->>FE: token(JWT) FE->>API: GET /bigScreen/home/dashboard(Authorization) ``` --- ## 2. 配置 ### 2.1 `application.yml` ```yaml bigscreen: login: enabled: false # 生产按需 true rsa-public-key: "" # Base64 公钥(可含换行,服务端会去除空白) rsa-private-key: "" # Base64 私钥(仅服务端,勿下发前端) username: screenviewer # 专用 sys_user 登录名 ``` | 项 | 说明 | | --- | --- | | `enabled` | `false` 时公钥接口与登录接口均返回「大屏免密登录未启用」 | | `rsa-public-key` / `rsa-private-key` | 须成对;开发环境可与 `ruoyi-ui/src/utils/jsencrypt.js` 中示例密钥一致 | | `username` | 对应 `sys_user.user_name`;不存在、已删除或已停用时登录失败;**无需**在若依中配置大屏菜单权限 | **密钥生成**:可使用 [在线 RSA 密钥对工具](http://web.chacuo.net/netrsakeypair)(JSEncrypt 格式);生产建议 **2048 位**独立密钥对。 --- ## 3. 接口 **Base Path**:`/bigScreen`(含 `server.servlet.context-path`、网关前缀须补齐)。 | 序号 | 说明 | 方法 | 路径 | 鉴权 | | --- | --- | --- | --- | --- | | 3.1 | 获取 RSA 公钥 | GET | `/bigScreen/publicKey` | 无(`@Anonymous`) | | 3.2 | 免密登录换 Token | POST | `/bigScreen/login` | 无(`@Anonymous`) | ### 3.1 获取 RSA 公钥 **请求**:无 Body。 **成功响应**(`AjaxResult`): ```json { "code": 200, "msg": "操作成功", "data": { "publicKey": "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH..." } } ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | `data.publicKey` | string | Base64 RSA 公钥,供 JSEncrypt `setPublicKey` | **失败**:未启用或未配置密钥时 `ServiceException`,统一错误提示。 ### 3.2 免密登录 **认证载荷**(二选一,优先请求头): | 方式 | 说明 | | --- | --- | | 请求头 | `X-Screen-Token: ` | | Body | `{ "token": "" }` | **前端步骤**: 1. 调用 **§3.1** 获取 `publicKey` 2. 生成标准 UUID(如 `crypto.randomUUID()`) 3. 使用 JSEncrypt 以公钥加密 UUID 字符串(明文 36 字符) 4. 将 JSEncrypt 返回的 Base64 密文作为 `token` 提交 **成功响应**(与 `POST /login` 一致): ```json { "code": 200, "msg": "操作成功", "token": "eyJhbGciOiJIUzI1NiJ9..." } ``` 后续看板请求:`Authorization: Bearer `。 **失败场景** | 场景 | 提示(示例) | | --- | --- | | 未启用 | 大屏免密登录未启用 | | 未配置密钥 | 大屏免密登录未配置 RSA 密钥 | | 密文为空 / 解密失败 / 明文非 UUID | 大屏登录认证失败 | | 专用账号不存在 | 大屏专用账号不存在 | | 账号已删除 / 已停用 | 若依标准 `user.password.delete` / `user.blocked` | --- ## 4. 实现要点 | 类 | 职责 | | --- | --- | | `ScreenLoginController` | 路由、`X-Screen-Token` / Body 取值 | | `ScreenLoginService` | 启用与密钥校验、解密验 UUID、校验专用用户状态、注入 `bigScreen:*:*`、签发 JWT | | `ScreenLoginPermissions` | 大屏免密登录固定权限集 | | `ScreenLoginRsaSupport` | RSA 加解密、`UUID.fromString` 格式校验 | | `ScreenLoginProperties` | 绑定 `bigscreen.login.*` | | `ScreenLoginConfig` | `@EnableConfigurationProperties` | **UUID 校验**:解密明文经 `UUID.fromString` 解析,须为标准 8-4-4-4-12 十六进制格式(大小写均可)。 **权限说明**: - 签发 Token 时写入 `permissions = ["bigScreen:*:*"]`,与若依 `SecurityUtils.hasPermi` 通配规则一致。 - `ruoyi-screen` 路由/导航为静态配置,登录成功即可访问全部大屏页面,**不依赖** `getInfo` / `getRouters`。 - 若调用 `GET /getInfo`,若依会按数据库角色刷新权限;大屏场景建议**仅使用免密登录返回的 Token**,勿混用普通登录。 **安全说明**: - 每次登录应生成**新 UUID** 再加密,避免重放固定密文(服务端当前仅校验格式,不存 nonce)。 - 私钥仅存服务端配置,禁止写入前端或版本库(生产用环境变量/密钥管理)。 - 大屏看板接口依赖有效 JWT;权限由免密登录注入,**不要求** `screenviewer` 在若依菜单中逐项授权。 --- ## 5. 前端示例(JSEncrypt) ```javascript import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' async function screenAutoLogin(apiBase) { const keyRes = await fetch(`${apiBase}/bigScreen/publicKey`).then(r => r.json()) const uuid = crypto.randomUUID() const encryptor = new JSEncrypt() encryptor.setPublicKey(keyRes.data.publicKey) const cipher = encryptor.encrypt(uuid) if (!cipher) throw new Error('RSA encrypt failed') const loginRes = await fetch(`${apiBase}/bigScreen/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: cipher }) }).then(r => r.json()) return loginRes.token } ``` --- ## 6. 交付清单 - [x] `ScreenLoginController`:`GET /bigScreen/publicKey`、`POST /bigScreen/login` - [x] `ScreenLoginService` + `ScreenLoginRsaSupport` + `ScreenLoginProperties` - [x] `application.yml` → `bigscreen.login.*` - [x] 单元测试:`ScreenLoginRsaSupportTest`、`ScreenLoginServiceTest`、`ScreenLoginControllerApiTest` - [x] `ruoyi-screen` 对接自动登录(按本方案调用公钥 + 登录接口) --- ## 7. 与各看板模块关系 | 模块 | 看板 Base Path | 登录后权限示例 | | --- | --- | --- | | 首页 | `/bigScreen/home` | `bigScreen:home:query` | | 交易销售 | `/bigScreen/tradeSales` | `bigScreen:tradeSales:query` | | 疫病风险 | `/bigScreen/epidemicRisk` | `bigScreen:epidemicRisk:query` | | 畜牧资源 | `/bigScreen/livestockResource` | `bigScreen:livestockResource:query` | | 共同富裕 | `/bigScreen/commonProsperity` | `bigScreen:commonProsperity:query` | 免密登录**不替代** JWT 鉴权;专用账号须在 `sys_user` 中存在且状态正常,**无需**配置大屏菜单权限。