status: apply created: 2026-07-27 complexity: 🔴复杂
sys_client.app_secret 当前以明文保存并直接比较,四个内置客户端的密钥还被写入初始化 SQL;管理端登录构建通过 VITE_APP_SECRET 把同一值打进浏览器产物。浏览器、H5 和分发式 App 无法安全保管客户端 Secret,因此继续把它们建模为机密客户端没有安全收益。
本变更完成以下结果:
none 公共客户端与 client_secret 机密客户端。SystemAuthServiceImpl#validateAndLoadClient 读取 userClient/appId/appSecret,当前通过 SysClient#getAppSecret().equals 比较。ClientServiceImpl#validateAppSecret 同样直接明文比较。SysClientController#create/update/getAppSecret 直接保存 DTO 中的 Secret,并对存储值做前四后四脱敏。login/index.vue、login/callback.vue 从 VITE_APP_SECRET 读取并随登录请求发送;.env.development 和 .env.production 均配置了非空值。sys_client.app_secret 为 NOT NULL,四个内置客户端均保存明文。forge-starter-auth 已提供 PasswordUtil.encrypt/matches BCrypt 封装,System 插件已依赖该 Starter。sys_client.app_secret 为 varchar(128),可容纳 {bcrypt} 标记和 BCrypt 摘要。SysClientMapper 当前只继承 BaseMapper,没有 Mapper XML。SysClientVO 不返回 appSecret,但 getMaskedAppSecret 会泄露摘要前后片段。VITE_* 会被 Vite 静态注入浏览器产物,不能作为机密凭据来源。id + 原存储值 条件更新。sys_client_auth_method 字典。app_secret,初始化 SQL 不再包含客户端 Secret。{bcrypt} 客户端 Secret 编解码器,使用 BCrypt 校验和常量时间遗留明文比较。clientCode + appId。VITE_APP_SECRET 读取和环境值,管理页按认证方式显示轮换输入。app_secret;轮换、清空和认证方式切换使用旧存储值 CAS,禁止并发恢复旧摘要。resultMap 和 JacksonTypeHandler 恢复 IP 白名单 JSON 映射。/auth/online/** 强制登录并逐接口校验权限;移除测试广播入口,所有管理操作通过租户内会话/用户记录定位。userTokens 兼容路径只返回会话 ID 和展示字段,列表 SQL 不读取 token_value。none 只能表示无法安全持有 Secret 的公共客户端;仍必须校验启用状态、客户端编码和 AppId。client_secret 创建时必须提供 Secret;更新时空值表示不轮换,非空表示原子替换摘要。{bcrypt}<60-char-hash>;未知标记或损坏摘要失败关闭。hasAppSecret/固定掩码,不返回摘要片段。| 操作 | 表名 | 字段/索引 | 说明 |
|---|---|---|---|
| 新增 | sys_client |
client_auth_method varchar(32) |
默认 client_secret,保护未知存量自定义客户端 |
| 修改 | sys_client |
app_secret varchar(128) NULL |
公共客户端允许为空 |
| 更新 | sys_client |
内置客户端 | pc/forge_report/app/h5 设置 none 并清空 Secret |
| 新增 | sys_dict_type/data |
sys_client_auth_method |
none、client_secret,tenant_id=1 |
迁移脚本使用 V1.0.53__harden_client_credentials.sql,所有结构和字典写入具备 information_schema/NOT EXISTS 防重复保护。回滚代码前必须先确认没有 {bcrypt} 摘要且公共客户端已恢复旧协议;不提供恢复历史明文的 SQL。
| 操作 | 接口 | 方法 | 变更内容 |
|---|---|---|---|
| 收紧 | /auth/login |
POST | 公共客户端不再接收固定 Secret;机密客户端校验 BCrypt/兼容明文 |
| 扩展 | /system/client |
POST/PUT | 新增 clientAuthMethod;Secret 采用新增必填、更新可选轮换语义 |
| 收紧 | /system/client/secret/{id} |
GET | 仅返回固定掩码或空值,不泄露摘要片段 |
| 收紧 | /auth/online/page、/list、/userTokens |
GET | 需要显式查询权限;不返回 Bearer Token,userTokens 兼容路径返回租户内会话记录 ID |
| 收紧 | /auth/online/kickout、/batchKickout |
POST | 需要独立操作权限;请求参数从原始 Token 改为租户内会话记录 ID |
| 删除 | /auth/online/test |
GET | 删除匿名 WebSocket 测试广播入口 |
forge-plugin-system:实体、DTO/VO、Mapper XML、凭据编解码、Service、认证和测试。forge-admin-ui:登录请求、环境模板、客户端管理页。forge-server/db/migration 和两份初始化 SQL:字段、字典和内置公共客户端数据。client:config:* 键。VITE_APP_SECRET 是协议修正,不能通过混淆或改名重新引入。test-spec.md。client_secret 并走兼容升级。client_auth_method,不通过 Secret 是否为空猜测客户端类型。PasswordUtil BCrypt,不引入新的哈希依赖。{bcrypt} 标记区分新旧格式,避免把普通 $2... 明文误判为摘要。client_secret,仅在管理员明确选择时创建公共客户端。/auth/login 及密码类入口由日志切面硬排除,动态日志配置不能重新开启敏感请求体记录。UPDATE 中写入,并同时校验旧认证方式与旧 Secret;空 Secret 元数据更新不写 app_secret,CAS 失败抛错并回滚。@SaCheckPermission。| Task | 状态 | 实际改动文件 | 备注 |
|---|---|---|---|
| Proposal | 完成 | 本变更四份 SDD 文档 | 当前用户消息确认按审计建议继续 |
| Task 1-4 | 完成 | Flyway/初始化 SQL、System Entity/DTO/VO/Mapper/Service/Auth/Controller、Admin UI/环境文件 | 未连接真实数据库 |
| Task 5 | 代码修复完成 | 在线鉴权/租户边界、会话 ID 协议、双字段原子 CAS、Token 日志清理、36 条目标测试 | 未执行真实 DB/Redis/E2E 或全量构建,保留部署门禁 |
resultMap/JacksonTypeHandler,环境合同覆盖 .env.test。/auth/online/test,登录排除与动态权限排除解耦,在线接口增加显式权限和租户内记录校验;列表 SQL 不读取 Token;客户端元数据、认证方式、Secret 合并为同一条双旧值 CAS。tenantId。