8.7 KiB
S03 生产配置、简历隐私与 Actuator 隔离设计
状态:设计已确认,待按独立检查点实施。
1. 目标与边界
S03 只处理三类高风险问题:
- 禁用仍保持原始状态的通用 Flyway 演示账号,并提供显式、一次性的管理员初始化能力;
- 让
resumeVisibility在企业读取简历文件的正式边界真正生效; - 将 Actuator 健康探针与指标端点隔离,防止普通业务用户读取运行指标。
S03 不修改 Access/Refresh Token 语义,不修改评分、排名、推荐、人才发现、邀约、Offer、申诉、消息撤回、简历上传与学生本人下载语义,不做性能重构,不进入 Q01/R01。
2. 已核实的现状
V1_1__seed_data.sql无环境隔离地插入已知 ADMIN、STUDENT、COMPANY 账号及公开口令哈希。- V1–V42 已经执行,不允许修改校验和;S03 使用新的 V43 迁移。
student_privacy_settings.resume_visibility已支持PRIVATE、APPLIED_COMPANIES、VERIFIED_COMPANIES,默认PRIVATE。PrivacySettingsService.canCompanyViewResume(...)没有生产调用方。- 该方法的
APPLIED_COMPANIES分支没有核对投递岗位归属companyId,不能直接作为授权边界使用。 - 企业候选人 DTO 当前主动排除了
resumeUrl;当前没有企业下载简历文件的正式接口。 /actuator/health/**和/actuator/info公开;metrics/prometheus被暴露后会落入普通authenticated(),任何登录用户均可能访问。
3. S03-A 历史种子账号治理
3.1 V43 迁移原则
新增且仅新增:
V43__disable_legacy_seed_accounts.sql
V43 只能禁用仍与 V1_1 原始指纹完全一致的三条种子账号。匹配至少同时包含:
- 原始手机号;
- 原始邮箱;
- 原始公开 bcrypt 哈希;
- 原始主角色;
is_active=1。
不得依赖固定 id=1/2/3。不得删除用户、角色、审计或业务数据,不得重置自增。不得修改已被运营人员改过手机号、邮箱、哈希或角色的账号。
命中后仅将 users.is_active 置为 0。保留记录与关联关系,用于历史数据完整性和审计追溯。由于认证签发链已拒绝 inactive 用户,已知账号不能再取得会话。
迁移必须幂等,并分别覆盖 ADMIN、STUDENT、COMPANY 三条原始种子。迁移报告只记录命中数量,不输出完整手机号、邮箱或哈希。
3.2 管理员一次性初始化
新增显式开关,默认关闭:
app.bootstrap.admin.enabled=${ADMIN_BOOTSTRAP_ENABLED:false}app.bootstrap.admin.phone=${ADMIN_BOOTSTRAP_PHONE:}app.bootstrap.admin.nickname=${ADMIN_BOOTSTRAP_NICKNAME:平台管理员}
初始化器仅在开关严格为 true 时装配。普通启动不得隐式创建管理员。
初始化规则:
- 手机号必须通过现有手机号格式校验;不得写日志、异常详情或审计 details。
- 数据库已有活动 ADMIN 时安全跳过,不创建第二个管理员。
- 数据库没有活动 ADMIN 且手机号不存在时,创建
active=true、verified=true的 ADMIN,并同时写入users.role与user_roles。 - 手机号已属于 STUDENT/COMPANY、inactive 用户或角色信息不一致时启动失败,禁止自动提权或复活。
- 相同配置重复启动必须幂等。
- 初始化成功后应提示固定文本,部署方必须移除开关;日志不得包含手机号。
- 多实例部署时只允许一个实例带初始化开关启动;数据库唯一手机号约束作为最后防线,冲突必须失败而不是吞掉。
不新增公网初始化接口,不接受请求参数创建 ADMIN,不设置默认密码。管理员仍使用正式短信认证流程;真实短信供应商未配置时不能以 Mock 绕过。
4. S03-B 简历隐私授权
4.1 唯一新增企业接口
新增:
GET /company/candidates/{applicationId}/resume
该接口返回文件流,不返回 resumeUrl、存储相对路径、绝对路径、学生用户 ID、手机号或邮箱。文件名必须继续经过 ResumeFilenameSanitizer。
除该接口外不新增按 studentUserId、candidateKey 或任意 URL 下载简历的入口。人才发现接口本轮不得直接下载简历。
4.2 授权顺序
必须在读取文件前依次完成:
- 从
SecurityContext取得当前用户,要求 COMPANY; - 定位当前用户拥有的 Company;
- 查询
applicationId及对应 Job; - 校验 Job 属于当前 Company;
- 取得投递对应的
studentUserId; - 按隐私设置判定可见性;
- 通过后才读取
student_profiles.resume_url和文件内容。
跨企业投递统一返回既有 403 语义,不通过差异响应泄露归属。隐私拒绝使用 FORBIDDEN(2002)。不存在的投递保持既有 APPLICATION_NOT_FOUND;授权通过但学生没有简历时使用既有 PROFILE_NOT_FOUND,不新增 ErrorCode。
4.3 可见性决策表
| 设置 | 企业条件 | 投递条件 | 结果 |
|---|---|---|---|
| 无设置记录/null | 任意 | 任意 | 403,按 PRIVATE 处理 |
| PRIVATE | 任意 | 包括已投递、面试、录用 | 403 |
| APPLIED_COMPANIES | 岗位属于当前企业 | SUBMITTED/VIEWED/INTERVIEW/HIRED | 200 |
| APPLIED_COMPANIES | 岗位属于当前企业 | WITHDRAWN/REJECTED | 403 |
| APPLIED_COMPANIES | 其他企业 | 任意 | 403 |
| VERIFIED_COMPANIES | 当前企业 verified=true 且拥有该投递岗位 |
任意合法投递状态 | 200 |
| VERIFIED_COMPANIES | 未认证企业或其他企业 | 任意 | 403 |
VERIFIED_COMPANIES 不是“知道学生 ID 即可查看”。它仍要求合法的候选业务上下文,本轮该上下文限定为当前企业拥有的 applicationId。
4.4 代码结构
将现有布尔方法收束为失败关闭的授权组件,例如 CompanyResumeAccessPolicy。授权组件必须校验 company ownership,不能依赖 Controller 先验,也不能把客户端传入的 studentUserId/companyId 当成可信事实。
文件读取放在独立 Service 方法中。Controller 只解析身份、路径参数、构建安全响应头。任何拒绝路径都不得调用 ResumeStorageService.read()。
企业候选人列表与详情 DTO 保持不含 resumeUrl。学生本人上传、下载、版本、导出接口完全不变。
5. S03-C Actuator 隔离
5.1 运行边界
推荐配置:
management.server.port=${MANAGEMENT_SERVER_PORT:8081}management.server.address=${MANAGEMENT_SERVER_ADDRESS:127.0.0.1}- 仅暴露
health,info,metrics,prometheus; health.show-details=never;health.show-components=never。
生产默认只绑定回环地址。需要采集 Prometheus 时,由部署层通过受控网络或反向代理访问管理端口,不通过公网业务端口开放。
5.2 业务端口行为
业务端口不得提供 /actuator/metrics 或 /actuator/prometheus。普通 STUDENT、COMPANY、ADMIN 的业务 JWT 不能把管理指标变成公网资源。健康检查继续提供最小 UP/DOWN 信息,不返回数据库、Redis、RabbitMQ、磁盘路径或连接池细节。
本轮不新增运维账号体系,不把 ADMIN 等同于基础设施运维身份。
6. 数据与副作用
允许的数据库变化仅为:
- V43 对完全匹配的旧种子账号执行
is_active: 1 -> 0; - 显式管理员初始化首次创建一条 ADMIN 用户和对应角色;
- 企业成功下载简历如需审计,只允许白名单动作与
applicationId/companyId,不得记录路径、文件名、学生 ID、联系方式或简历内容。
简历访问不得修改 student_profiles、resume_versions、投递、评分、排名、推荐、邀约、消息、通知或风险表,不发送 RabbitMQ、短信或邮件。
7. 契约与兼容性
- S02 基线为 175 paths / 213 operations。
- 新增一个企业简历下载接口后,预期为 176 paths / 214 operations;最终以运行时 OpenAPI 导出为准。
- 不修改既有响应字段,不把
resumeUrl加回任何企业 DTO。 - 不新增 ErrorCode。
- V43 是唯一新增迁移,V1–V42 不得修改。
8. 验收门槛
必须同时通过:
- V43 在 H2 快速层和真实 MySQL 8 执行成功;
- 仅完全匹配的三条原始种子账号被禁用,改过任一指纹的账号不受影响;
- 管理员初始化关闭、成功、重复、已有 ADMIN、手机号冲突、并发冲突场景;
- 三种简历可见性及所有投递状态、跨企业、未认证企业、ADMIN/STUDENT/匿名权限矩阵;
- 拒绝路径零文件读取,企业 JSON/响应头无路径及个人信息泄漏;
- Actuator 业务端口与管理端口真实启动验证;
- 全量测试和
mvn clean verify零失败、零错误、零跳过; - 静态与运行时 OpenAPI 一致;
- 真实 MySQL/Docker 黑盒通过后才能冻结。