# 功能测试报告_接口鉴权与AI数据脱敏加固_2026-09-14 > 模块:系统安全(后端鉴权 / 跨域 / AI 数据脱敏 / 日志脱敏) 测试人:Codex 日期:2026-09-14 ## 一、功能说明 针对本日安全自查发现的四类风险做加固(**这是安全整改,不是新功能**): | 序号 | 原风险 | 本次处置 | | --- | --- | --- | | 1 | 后端 `/api/**` 全部 `permitAll`,任何人无需登录即可调用,含"按报表期清空数据"等敏感接口 | 新增 `JwtAuthFilter`,除登录/验证码白名单外,`/api/**` 必须携带有效 `Bearer` 令牌,否则返回 JSON 401;`application.yml` 的 `jwt.secret` 由可猜测的固定串改为 64 位随机串 | | 2 | CORS `allowedOriginPattern("*")` 且 `allowCredentials(true)`,任意网站可携带凭证调用本机接口 | 改为只允许配置项 `app.cors.allowed-origins` 中列出的来源(默认 `http://localhost:8080,http://127.0.0.1:8080`),并关闭 `allowCredentials` | | 3 | AI 查库接口可传 `columns:"*"` 放开整表,企业名称、统一社会信用代码、单位负责人、填表人、联系电话明文发往 DeepSeek | ① 去掉 `"*"` 放开整表的口子(改为只返回默认列);② 屏蔽 7 个字段(`unified_credit_code`/`contact_person`/`contact_phone`/`unit_leader`/`stats_leader`/`report_unit`/`enterprise_code`),既不可查也不可作过滤/排序条件,且不再出现在给模型的表结构提示里;③ 外发前把企业名称换成 `Company1…` 别名,自由文本中的企业名称与 11 位手机号一并处理,回答返回前端前还原 | | 4 | MyBatis-Plus 默认 `StdOutImpl` 把整行数据(含企业名称、联系电话)打进 `backend.log` | 显式改为 `Slf4jImpl`,SQL 日志降为 DEBUG 级别,默认不输出 | 改动文件(8 改 1 新增):`security/filter/JwtAuthFilter.java`(新增)、`security/config/SecurityConfig.java`、`common/config/CorsConfig.java`、`llmintegration/service/DbQueryExecutor.java`、`llmintegration/service/DesensitizeService.java`、`llmintegration/service/LLMService.java`、`traffic-audit-web/src/views/AuditResult.vue`、`traffic-audit-web/src/views/DataImport.vue`、`application.yml.example`、`docs/database.md`。 **连带修复**:前端 `AuditResult.vue`(审核结果导出)与 `DataImport.vue`(下载导入模板)原本用 `window.open('/api/...')` 直连接口,浏览器跳转不会带 `Authorization` 头,开启鉴权后会 401;已改为走 axios 实例取 blob 再触发下载(报表生成页原本就是这种写法)。 ## 二、测试环境 - 后端 8090(`mvn -o -q compile` 通过,EXIT=0,重启后就绪)、前端 8080(dev 编译成功)。 - 数据库:本机 `traffic_audit`(MySQL 3308)。 - 抓包手段:把 `deepseek.api-url` 临时指向本地假 DeepSeek 服务(`127.0.0.1:18080`),完整捕获每一次发往大模型的请求体,**测试后已恢复为真实地址并重启**。 ## 三、用例与结果 | 编号 | 用例 | 输入 | 预期 | 实际 | 结论 | | --- | --- | --- | --- | --- | --- | | S1 | 未登录访问业务接口 | 无令牌 GET `/api/user/list`、`/api/data/list`、`/api/audit/export`、`/api/report/export/turnoverRank`、`/api/templates/...`、`/api/llm/chat` | 全部 401 | 6 组接口全部返回 401 JSON | 通过 | | S2 | 登录白名单不受影响 | 无令牌 GET `/api/auth/captcha-config`、POST `/api/auth/login` | 可访问 | 登录返回 200,令牌 156 位 | 通过(正向) | | S3 | 携带令牌正常使用 | `admin/123456` 取令牌后调 `/api/user/list`、`/api/audit/export`、`/api/report/export/turnoverRank` | 200 | 200 / 200(59965 字节)/ 200(14589 字节) | 通过 | | S4 | 令牌无效时拒绝 | 伪造/过期 `Bearer` | 401 | 401 | 通过(负向) | | S5 | 跨域白名单生效 | 带 `Origin: http://localhost:8080` 请求 | 返回该来源 | 200,`Access-Control-Allow-Origin: http://localhost:8080` | 通过 | | S6 | 跨域非白名单被拦 | `Origin: http://evil.example.com`、`http://127.0.0.1:9999` | 拒绝 | 均 403,未返回放行头 | 通过(负向) | | S7 | 预检请求 | 无令牌 `OPTIONS` + `Origin: localhost:8080` | 200 且带放行头 | 200,允许来源正确 | 通过 | | S8 | AI 查全表被限制 | 让模型发起 `columns:"*"` 查询 `h2032_enterprise_monthly` | 只返回默认列 | 抓包显示返回列仅含默认列,**不含**任何联系人/电话/信用代码字段 | 通过 | | S9 | AI 指定敏感列被拒 | `columns:["enterpriseName","contactPhone","unifiedCreditCode"]` | 拒绝 | 返回 `该字段不允许查询: contactPhone` | 通过(负向) | | S10 | AI 用敏感列做过滤被拒 | `filters:{"contactPhone":"180****8880"}` | 拒绝 | 返回"列名不合法",可用列清单中已无联系人/电话/信用代码 | 通过(负向) | | S11 | 企业名称脱敏外发 | 观察抓包中 tool 结果的 `enterpriseName` | 应为别名 | 实际为 `Company1`、`Company2` | 通过 | | S12 | 回答返回前还原 | AI 回答文案 | 显示真实企业名 | `unmaskAll` 还原(代码路径 + 抓包比对) | 通过 | | S13 | 日志不再落整行数据 | 重启后执行登录、列表、导出、审核结果导出 | 无行转储 | `<=` 行 0 条、"Total:" 0 条、手机号样式 0 条(整改前单次查询即数百条) | 通过 | | S14 | 前端编译 | 改动 2 个 .vue 后 dev 编译 | 无错误 | `Compiled successfully` ×2 | 通过 | | S15 | 前端带令牌下载 | 审核结果导出、模板下载改为 blob 方式 | 不再 401 | 接口侧带令牌返回 200;前端编译通过(浏览器终验见"遗留") | 部分(页面待人工点验) | ## 四、发现的问题与处置 1. **MyBatis 日志第一轮没改掉**:MyBatis-Plus 在没有显式配置时**默认就是 `StdOutImpl`**,仅删除配置项无效,仍把整行数据打进日志。已显式改为 `Slf4jImpl` 并复测(S13)。 2. **前端两处 `window.open` 会因鉴权失效**:`AuditResult.vue` 的审核结果导出、`DataImport.vue` 的模板下载。已改为 axios 取 blob 后下载(原报表生成页的同款写法)。 3. **`jwt.secret` 可猜测**:鉴权一旦真正启用,固定且写死的密钥等于可伪造令牌。已换成 64 位随机串(`application.yml` 未入库,仓库内只有占位符示例)。**副作用:所有已登录用户的令牌失效,需重新登录。** 4. **`llm_desensitize_map` 表与实际实现不符**:该表设计为可逆脱敏映射落库,但代码从未引用(0 行)。本次采用内存态脱敏,已在 `docs/database.md` 第七章补充"实现说明",注明该表为预留、当前未启用,避免文档与实现不一致。 ## 五、结论 四类风险已全部处置并通过接口级、产物级与"报文级"验证:未登录无法访问任何业务接口;跨域仅放行配置来源;发往 DeepSeek 的报文里企业名称已换为别名,联系人、电话、统一社会信用代码等字段完全无法被查询或外发;后端日志不再落整行业务数据。 遗留(不影响本次结论): - 3D 验证码仍默认关闭(`app.captcha.required: false`),生产部署前建议开启; - `sys_role` / `sys_user_role` 仍为空表,角色权限未落地,目前"权限"仍只等于"报表页显示哪几个模块"; - 审核结果导出、模板下载两处前端改动建议在浏览器点验一遍。