# 数字人民币系统:单后端 SSO 与只读用户设计 ## 1. 目标与边界 数字人民币系统在现有 `digital-rmb-backend` 中同时实现三个登录入口: - 主平台跳转后的单点登录; - 用户直接访问系统后的学校 CAS 统一认证登录。 - 使用主平台账号密码的本地直接登录。 主平台不做改造。用户、教师/学生身份及角色均以主平台为唯一来源;数字人民币系统仅维护受控的只读快照,不提供用户、角色或用户角色的人工增删改接口。业务角色只有 `TEACHER` 与 `STUDENT`。 “只读”指面向用户、管理端和公开 API 只读。内部同步任务可写入本地镜像表,否则无法完成主平台数据复制。 ## 2. 已验证的现状 主平台使用学校 CAS 完成统一认证,在登录后签发自定义 Token;它不是 OAuth2/OIDC 身份提供方。 - CAS 配置位于主平台 `web/src/main/resources/application.properties`; - CAS Filter 配置位于 `web/src/main/java/cn/jlw/Interceptor/CasConfig.java`; - 自定义 Token 的生成和 HMAC 验证位于 `web/src/main/java/cn/jlw/token/TokenService.java`; - CAS 用户到教师/学生的映射位于 `web/src/main/java/com/ibeetl/jlw/service/CasUserLoginService.java`; - 主平台用户、学生、教师表分别为 `core_user`、`student`、`teacher`。 主平台 Token 的 HMAC 密钥由教师或学生记录的 `addTime` 派生,不能使用 PEVC 示例中的固定共享密钥方式验签。PEVC 示例采用的也是“父系统 JWT + 自定义共享密钥”协议,并非 OAuth2。 ## 3. 总体架构 不新增独立部署的认证服务。认证、防腐适配和本系统 JWT 都在现有 Spring Boot 后端内实现。 ``` 主平台 ──携带自定义 Token 跳转──> digital-rmb-backend ──签发本系统 JWT──> 前端 学校 CAS ──CAS Ticket 认证────────> digital-rmb-backend ──签发本系统 JWT──> 前端 主平台只读库 ──密码明文(瞬时)─────> BCrypt ──> sys_user.password_hash digital-rmb-backend ──SELECT only──────────────────────────> 主平台数据库 ``` 主平台 Token 适配与 CAS 协议放入 `platform-integration` 防腐层。`identity` 限定上下文维护只读快照与角色投影;`security` 限定上下文签发、校验和撤销数字人民币系统自己的 JWT。业务域只依赖 `CurrentActor`(主平台用户 ID、教师/学生角色、状态),不依赖 CAS 或主平台 Token 细节。 ## 4. 登录流程 ### 4.1 主平台单点登录 1. 主平台既有跳转链接访问 `GET /api/v1/auth/sso?token=...`; 2. 后端按 Token 中未验签的用户 ID 和身份类型定位主平台候选记录; 3. 后端读取候选教师或学生记录的 `addTime`,按主平台算法完成 HMAC 验签; 4. 后端读取主平台用户状态、教师/学生资料、角色及密码,刷新本地只读快照,并将密码立即 BCrypt 编码为本地哈希; 5. 后端生成仅一次、短时有效的登录兑换码,重定向到配置的前端回调地址; 6. 前端调用 `POST /api/v1/auth/session/exchange` 兑换本系统 JWT 与刷新令牌。 ### 4.2 直接访问的独立登录 1. 用户访问 `GET /api/v1/auth/cas/login`; 2. 后端将浏览器重定向至学校 CAS 登录页,并使用固定的回调地址作为 CAS `service`; 3. CAS 将 `ticket` 回调至 `GET /api/v1/auth/cas/callback`; 4. 后端调用学校 CAS 的 `serviceValidate` 接口验证 Ticket,取得学校账号; 5. 后端以学校账号查询主平台 `core_user`,仅接受教师或学生,再刷新快照及本地密码哈希; 6. 后端创建登录兑换码,前端兑换本系统 JWT 与刷新令牌。 用户已在学校 CAS 或主平台登录时,第 2 步无感完成;没有 CAS 会话时,用户在学校认证页输入统一账号密码。 ### 4.3 本地账号密码登录 1. 用户调用 `POST /api/v1/auth/login`,提交主平台账号与密码; 2. 本系统仅以 `sys_user.password_hash` 的 BCrypt 哈希校验密码,成功后签发本系统 JWT; 3. 初次使用前,用户通过主平台 SSO/CAS 成功登录时会初始化本地哈希;`local` 环境还会在启动时为配置账号 `tzs001` 尝试初始化; 4. 密码变更后,下一次成功 SSO/CAS 登录会刷新本地哈希。密码明文绝不进入快照表、JWT、响应、日志或迁移脚本。 ### 4.4 退出 `POST /api/v1/auth/logout` 只撤销本系统刷新令牌与本地会话;默认不退出学校 CAS 或主平台,避免跨系统连带登出。 ## 5. 本地只读投影 | 表 | 用途 | 写入者 | | --- | --- | --- | | `platform_user_snapshot` | 主平台用户 ID、账号、姓名、状态、同步时间 | 内部同步任务 | | `role` | 固定角色 `TEACHER`、`STUDENT` | 数据库迁移 | | `user_role` | 主平台用户到教师/学生角色的镜像 | 内部同步任务 | | `platform_identity_link` | 主平台用户 ID 与本系统主体的稳定映射 | 内部同步任务 | | `auth_refresh_token` | 本系统可撤销刷新令牌摘要 | 认证模块 | | `login_exchange_code` | 一次性前端兑换码摘要及过期时间 | 认证模块 | 角色映射以主平台 `core_user.job_type1` 为准:`JT_S_02` 映射为 `TEACHER`,`JT_S_03` 映射为 `STUDENT`。其他身份、已删除用户、禁用教师或禁用学生均拒绝登录。 主平台用户 ID 是稳定关联键,不按姓名、手机号或可变用户名合并用户。同步采用首次全量、定时增量和每次成功登录即时刷新;本系统的用户与角色接口均为只读。 ## 6. 安全规则 ### 6.1 主平台 Token 1. 限制 Token 长度、字符集和主平台约定的 JWT 加登录时间戳结构; 2. 未验签声明只用于查询候选记录; 3. 严格使用候选学生或教师记录的 `addTime` 重建 HMAC 密钥并验证签名; 4. 比较 Token 身份类型、主平台 `job_type1` 和目标资料表的一致性; 5. 检查状态,并对 Token 的 SHA-256 指纹作一次性消费;跳转 Token 最长有效窗口为 120 秒; 6. 验签后绝不透传主平台 Token,业务接口只接受本系统 JWT。 ### 6.2 URL 与前端回调 跳转 Token 仅由 SSO 入口接收,之后立即重定向到不含 Token 的 URL。相关响应设置 `Cache-Control: no-store` 和 `Referrer-Policy: no-referrer`;反向代理、访问日志和异常日志必须脱敏 `token` 参数。 前端回调地址只能来自白名单配置,禁止请求参数指定任意重定向地址。回调中只携带一次性兑换码,不能携带主平台 Token 或本系统 JWT。 ### 6.3 CAS CAS `service` 地址必须固定并使用 HTTPS。Ticket 仅能向配置的 CAS `serviceValidate` 地址验证;XML 解析必须关闭外部实体和 DTD,防止 XXE。CAS 返回的账号只是身份索引,仍必须通过主平台只读数据确认教师/学生角色和状态。 ### 6.4 数据库权限 后端使用独立的主平台只读账号,仅对 `core_user`、`student`、`teacher` 及必要关联表授予 `SELECT`。禁止授予 `INSERT`、`UPDATE`、`DELETE`、`CREATE`、`ALTER` 或 DDL 权限。 本系统只允许主平台教师和学生使用本地账号密码登录。密码哈希仅由内部主平台凭据投影写入;不提供用户、角色、用户角色或密码的管理接口。主平台数据库账号除身份表外还需对 `core_user.PASSWORD` 保有只读 `SELECT` 权限。 ## 7. 对外接口 - `GET /api/v1/auth/sso?token=...`:主平台单点登录入口; - `GET /api/v1/auth/cas/login`:发起学校 CAS 认证; - `GET /api/v1/auth/cas/callback?ticket=...`:固定 CAS 回调; - `POST /api/v1/auth/login`:以已初始化的主平台账号密码直接登录; - `POST /api/v1/auth/session/exchange`:以一次性兑换码换取本系统 JWT; - `GET /api/v1/auth/me`:读取当前用户与教师/学生角色; - `POST /api/v1/auth/logout`:撤销本系统会话。 Swagger 只公开上述认证和当前用户读取接口;不公开用户、角色、用户角色 CRUD。 ## 8. 验收与测试 - 有效主平台教师 Token 返回 `TEACHER`,学生 Token 返回 `STUDENT`; - 伪造、篡改、过期、重放、身份不匹配或状态异常的 Token 均被拒绝; - CAS 有会话和无会话两种直接登录路径均可完成; - CAS Ticket 验证失败、CAS 返回非教师/学生、主平台查询失败均返回受控错误; - 本地账号密码仅校验 BCrypt 哈希;密码明文不出现在本系统存储、JWT、响应或日志; - 登录兑换码一次性、短时有效,不能被重放; - 主平台数据库账号仅能 SELECT,任何写操作都失败; - 用户、角色和用户角色的写接口不存在或被拒绝; - Swagger 不展示用户/角色 CRUD,且不会记录 Token。