# 功能测试报告:密钥外部化与打包去密(2026-09-26) > 日期:2026-09-26 模块:配置/打包(traffic-audit-server 配置分层 + .gitignore + 部署说明) 测试人:Codex > 起因:检查部署包 `E:\试验\traffic-audit-deploy-20260913` 时发现——外层 `backend/application.yml` 是干净的占位版,但 **jar 内置的 `BOOT-INF/classes/application.yml` 里带着真实密钥**(DeepSeek API key、本机数据库口令、JWT 密钥)。 ## 一、功能说明(改动) 1. **进 jar 的配置一律占位符**:`traffic-audit-server/src/main/resources/application.yml` - `spring.datasource.url: ${TRAFFIC_DB_URL:...}`、`username: ${TRAFFIC_DB_USER:root}`、`password: ${TRAFFIC_DB_PASSWORD:}`(默认空) - `jwt.secret: ${TRAFFIC_JWT_SECRET:traffic-audit-dev-jwt-secret-change-me}` - `deepseek.api-key: ${DEEPSEEK_API_KEY:}`(默认空) → 打包出的 jar **不再包含任何真实密钥**。 2. **本机开发的真实值单独放**:新建 `traffic-audit-server/config/application.yml`(保存数据库口令、JWT、DeepSeek key), Spring Boot 默认加载 `file:./config/application.yml`,优先级高于 classpath 的同名配置 → 本机 `mvn spring-boot:run` 行为不变。 该文件已加入 `.gitignore`(第 106 行),**既不进仓库、也不进 jar**。 3. **部署说明补充**:`docs/打包部署说明.md` 增加"密钥与配置"一节(含服务器上轮换 key 的步骤)。 ## 二、测试环境 后端 `mvn -q spring-boot:run -f traffic-audit-server/pom.xml`(8090);MySQL 本机 `localhost:3308/traffic_audit`;账号 admin/123456。 ## 三、用例表 | 编号 | 输入 | 预期 | 实际 | 结论 | | --- | --- | --- | --- | --- | | TC-1 | 启动后端(本机覆盖文件存在) | 正常启动 | 日志出现 `Started TrafficAuditApplication` | 通过 | | TC-2 | `POST /api/auth/login`(admin/123456) | 库口令取自 `config/application.yml` | 登录成功、返回 token(若覆盖文件未生效,占位符默认空口令会连不上库) | 通过 | | TC-3 | `POST /api/llm/chat`("只回复两个字:收到") | DeepSeek key 取自 `config/application.yml` | HTTP 200、回答"收到" | 通过 | | TC-4 | 进 jar 的 `application.yml` 内容 | 无真实密钥 | 三处均为 `${...}` 占位符(口令与 api-key 默认空) | 通过 | | TC-5 | `git check-ignore` | 本机真实配置不进库 | `.gitignore:106 traffic-audit-server/config/application.yml` | 通过 | | TC-6 | `git grep -E "sk-[A-Za-z0-9]{16,}"` | 跟踪文件里无密钥 | 无命中(另:09-23 前核查过 git 历史也无命中) | 通过 | | TC-7 | 仓库内两份部署模板(`deploy/.../backend/application.yml`、`_deploytest/application.yml`) | 无真实密钥 | 均为「请改成你的数据库密码」+ `api-key: ""` | 通过 | ## 四、发现的问题与处置 1. **jar 内置配置带真实密钥**(本次根因):Spring Boot 外部配置优先级更高,所以线上运行时用的是外部值、功能上没受影响;但 jar 可解压,明文密钥就在里面(二进制里搜不到 `sk-` 是因为被 zip 压缩,解压即见)。 → 已按上述方案根治:**以后打包不会再把密钥打进 jar**。 2. **已带出去的那份 jar 无法收回**:里面的 key 仍在。处置建议(必须做): - 到 DeepSeek 控制台**新建一个 key 并删除旧的**(旧 key 立即失效,jar 里的那份就废了); - 去那台服务器上把外部 `backend/application.yml` 的 `deepseek.api-key` 换成新 key(或直接留空,AI 分析就不用); - 重启后端(`start-backend.cmd`),在页面点一次 AI 分析确认可用。 3. 本机数据库 root 口令同样在旧 jar 里:若那台服务器用过同一口令,建议一并改掉(部署模板本来就要求现场改)。 ## 五、结论 配置分层改造完成并通过 7 条用例:进 jar 的只有占位符,真实值仅存在于本机未跟踪文件或环境变量;本机开发与线上部署行为均未退化。轮换 key 的步骤已写入部署说明。 ## 六、第二轮(同日晚,用户同意后追加):本机也不留明文 1. **本机真实值改为 Windows 用户环境变量**(不再落文件): ```powershell setx TRAFFIC_DB_PASSWORD "本机 MySQL 口令" setx DEEPSEEK_API_KEY "DeepSeek key" ``` `traffic-audit-server/config/application.yml` 改成"只有说明、无明文"的注释文件(值全部由环境变量提供)。 2. **start-dev.ps1 加注册表兜底**:若当前 shell 是旧进程、拿不到新设变量,则从 `HKCU\Environment` 读取并注入后端进程,避免"文件里没有、环境变量也读不到"的空档。 3. **清理仓库内含 key 的历史文件**(未跟踪/已忽略,非提交内容): | 文件 | 命中 | | --- | --- | | `_shots/all_events.json` | 13 处 | | `_dep_readme.txt` | 1 处 | → 全部替换为 `sk-REDACTED`,复查全仓库**已无明文 key**。 ### 追加用例 | 编号 | 输入 | 预期 | 实际 | 结论 | | --- | --- | --- | --- | --- | | TC-8 | `start-dev.ps1`(在未继承新变量的 shell 里) | 自动从注册表载入 | 输出"已从用户环境变量载入 TRAFFIC_DB_PASSWORD / DEEPSEEK_API_KEY" | 通过 | | TC-9 | 启动后登录 `admin/123456` | 库口令取自环境变量 | 登录成功、token 156 字符 | 通过 | | TC-10 | `POST /api/llm/chat` | key 取自环境变量 | HTTP 200、回答"收到" | 通过 | | TC-11 | 全仓库扫描 `sk-[A-Za-z0-9]{16,}` | 无明文 | 清理后复查为 0 | 通过 | | TC-12 | `traffic-audit-server/config/application.yml` | 无明文 | 仅注释说明(值走环境变量) | 通过 | > 说明:环境变量本身仍是本机明文(存在注册表 `HKCU\Environment`),适合单机开发;协作/生产请用部署机外部配置或密钥管理工具。轮换 key 仍是根治泄露的唯一手段。