8.6 KiB
数字人民币系统:单后端 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──> 前端
digital-rmb-backend ──SELECT only──────────────────────────> 主平台数据库
主平台 Token 适配与 CAS 协议放入 platform-integration 防腐层。identity 限定上下文维护只读快照与角色投影;security 限定上下文签发、校验和撤销数字人民币系统自己的 JWT。业务域只依赖 CurrentActor(主平台用户 ID、教师/学生角色、状态),不依赖 CAS 或主平台 Token 细节。
4. 登录流程
4.1 主平台单点登录
- 主平台既有跳转链接访问
GET /api/v1/auth/sso?token=...; - 后端按 Token 中未验签的用户 ID 和身份类型定位主平台候选记录;
- 后端读取候选教师或学生记录的
addTime,按主平台算法完成 HMAC 验签; - 后端读取主平台用户状态、教师/学生资料及角色,刷新本地只读快照;首次投影的本地用户生成随机 BCrypt 密码;
- 后端生成仅一次、短时有效的登录兑换码,重定向到配置的前端回调地址;
- 前端调用
POST /api/v1/auth/session/exchange兑换本系统 JWT 与刷新令牌。
4.2 直接访问的独立登录
- 用户访问
GET /api/v1/auth/cas/login; - 后端将浏览器重定向至学校 CAS 登录页,并使用固定的回调地址作为 CAS
service; - CAS 将
ticket回调至GET /api/v1/auth/cas/callback; - 后端调用学校 CAS 的
serviceValidate接口验证 Ticket,取得学校账号; - 后端以学校账号查询主平台
core_user,仅接受教师或学生,再刷新快照; - 后端创建登录兑换码,前端兑换本系统 JWT 与刷新令牌。
用户已在学校 CAS 或主平台登录时,第 2 步无感完成;没有 CAS 会话时,用户在学校认证页输入统一账号密码。
4.3 本地账号密码登录
- 用户调用
POST /api/v1/auth/login,提交已由本系统初始化的账号与密码; - 本系统仅以
sys_user.password_hash的 BCrypt 哈希校验密码,成功后签发本系统 JWT; - Flyway V3 迁移内置
tzs001(平台用户 ID487、STUDENT)的 BCrypt 哈希、快照和角色关联; - SSO/CAS 自动建号仅生成随机 BCrypt 密码,不读取主平台密码。密码明文绝不进入快照表、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
- 限制 Token 长度、字符集和主平台约定的 JWT 加登录时间戳结构;
- 未验签声明只用于查询候选记录;
- 严格使用候选学生或教师记录的
addTime重建 HMAC 密钥并验证签名; - 比较 Token 身份类型、主平台
job_type1和目标资料表的一致性; - 检查状态,并对 Token 的 SHA-256 指纹作一次性消费;跳转 Token 最长有效窗口为 120 秒;
- 验签后绝不透传主平台 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 权限。
本系统只允许教师和学生使用本地账号密码登录。内置账号通过 Flyway 迁移写入 BCrypt 哈希;SSO/CAS 自动建号使用随机 BCrypt 密码,不提供用户、角色、用户角色或密码的管理接口。主平台数据库账号只需读取身份和角色所需字段,不读取 core_user.PASSWORD。
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。