编辑 | blame | 历史 | 原始文档

功能测试报告_接口鉴权与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 仍为空表,角色权限未落地,目前"权限"仍只等于"报表页显示哪几个模块";
- 审核结果导出、模板下载两处前端改动建议在浏览器点验一遍。