Files
pj_service_cloud/docs/file-access/verification.md
T

17 KiB
Raw Blame History

五端文件签名验证记录

验证日期:2026-10-29。范围为本地五个项目的实现、定向回归、构建和已登录后台页面检查。未发布服务、修改 Nacos、开启生产拦截、更新真实设备、执行数据库迁移或提交 Git。

已完成

验证 结果
Java 请求级与业务回归 41 项通过,0 失败;覆盖 UPMS 文件校验/读取、版本地址、钱包和硬件导出,同时编译相关公共模块及 canteen
后台自动测试 14 项通过,包含文件协议与已有部门导出测试
H5 文件自动测试 14 项通过
小程序文件自动测试 14 项通过
跨语言协议 Java、三端 TypeScript、Dart 使用相同公开向量;校验 UTF-8 文件名、秒级时间戳、Base64URL 原文和 HMAC 结果
URL 与运行时 覆盖中文、空格、加号、百分号编码、历史域名、相对/绝对地址、已有 /api 前缀、重复签名、第三方地址;前端测试执行实际运行时包装器,H5/小程序测试执行富文本预览组件的 JS 方法;Java 使用真实 SpringMvcContract/Feign 客户端与内存传输验证文件名字面 %2F/%2B、重复拦截和参数顺序
后台生产构建 pnpm build:prod 通过
H5 构建 pnpm build:h5 通过
微信小程序构建 pnpm build:mp-weixin 通过
Flutter 定向回归 56 项通过;含签名、安装包一次重试、人脸同步计划/流水线/失败记录/缓存、主副屏余额回归
Android Release FVM Flutter 3.27.4 构建通过,使用 .file-access/production.json;APK 约 101.9 MB
新增后台 TypeScript 工具 独立严格类型检查通过
配置生成器 在临时目录重复运行,确认幂等、保留已有文件;开发/测试/生产配置和各客户端密钥相互独立
密钥与原有变更 扫描五个项目全部已跟踪及未忽略候选文件,未发现生成密钥泄漏;后台原有补丁可反向校验,Java 原有补丁除追加忽略规则产生的上下文差异外可反向校验
Diff 检查 保留原有 CRLF 文件格式;使用 git -c core.whitespace=cr-at-eol diff --check 通过

APK 位置(相对本仓库):../pj_supermarket_cloud/build/app/outputs/flutter-apk/app-release.apk。本次构建尚未在真实终端安装验证;发布前须核对包内环境与服务端配置一致。

Java 定向测试及各端构建命令见 接入与发布说明。本机详细日志保存在 /tmp/pj-file-access-work/,属于临时验证资料,不随仓库发布。

静态检查与既有问题

使用任务开始前保存的补丁还原临时代码基线,与当前代码运行同一检查后比较:

项目 当前结果与基线比较
后台 vue-tsc 6 条既有解析错误,均在 src/views/admin/system/tenant/index.vue:83;与基线完全一致
H5 vue-tsc 4 条既有解析错误,在 src/pages/tabbar/~individualOrder.vue:78-79;与基线完全一致
小程序 vue-tsc 17 条既有 TS6504(JS Vue 来源与 allowJs 配置);规范化临时目录路径后与基线完全一致
Flutter analyze --no-pub 69 条提示/警告;忽略因新增代码产生的行号偏移后,诊断集合与基线完全一致,无新增项

因此不能把本次结果表述为“全项目类型检查通过”。H5/小程序构建仍有 TM UI 类型导出和循环依赖等构建提示;构建已成功,未在本次跨端文件改造中整理无关依赖。

已登录后台检查

使用用户提供的 http://localhost:8888/ 已登录会话,检查文件管理页:

  • 本系统有效图片的缩略图与放大预览加载成功,资源请求带 appId/kid/ts/sign,fileName 位于最后。
  • 实测发现并修复前端 /api 前缀被重复保留的问题:当前开发图片服务器不带 /api,相对旧路径应转换为 /admin/sys-file/oss/file。配置含 /api 的部署仍保留一次前缀。
  • 旧头像的一条原始文件链接返回 HTTP 200、内容长度为 0,属于当前运行后端/存储的既有异常。新 Java 读取代码已覆盖不存在和存储异常状态,但尚未发布,不能声称现场问题已修复。
  • 当前后端匿名 /admin/sys-file/oss/access-info 返回 424 鉴权错误,说明运行环境尚未具备本次匿名校时接口;签名客户端会保留本地时间/最近偏移,不降级匿名读取。
  • 点击素材下载后未出现页面错误,但浏览器下载事件未在等待窗口内返回。浏览器工具自动安全检查拒绝打开 chrome://downloads/(仅允许 HTTP/HTTPS),未采用其他方式绕过此限制,下载文件实际落盘未确认。

当前运行后端仍接受裸文件链接。上述浏览器结果证明前端签名与展示链路可用,不代表新后端严格校验已在线生效。

发布前仍需完成

  1. 核对每个环境的公网基础地址、历史域名、密钥和活动 kid;Java 不会自动加载 .file-access/*.yml,须通过 Nacos 或 SPRING_CONFIG_ADDITIONAL_LOCATION 加载。先以 observe 发布 UPMS、integrated 和所有实际调用文件服务的模块。
  2. 用发布后的后端确认匿名校时可达,执行 GET/HEAD、错误参数、错误/过期签名、旧 Dart 兼容、404/500、私有缓存及服务间读取联调。
  3. 在真实旧 Flutter 安装包上确认图片请求 UA、图片显示及 Android 原生 APK 下载。新版包确认主副屏、缓存命中、人脸重复同步/离线恢复、取证、打印、手动/自动更新;不能用本地源码或单元测试替代设备验收。
  4. 微信真机验证登录前 Logo/背景、长时间停留和恢复前台、图片预览、文件下载及打开文档;核对微信文件域名配置。后台与 H5 补充对应实际业务操作验收。
  5. 至少观察一个完整业务日,确认已知入口无遗漏后再切 enforce;根据设备版本台账确认升级覆盖后再关闭旧 Dart 图片兼容。

当前默认保持 observe 与旧 Dart 图片兼容开启。回滚方法是恢复 observe 并保留诊断日志;业务引用和人脸记录未持久化临时签名,无需数据库回滚。

后台图片闪烁修复补充

2026-10-29 修复后台单张图片失败重试清空全局签名缓存的问题。重试只刷新当前文件,同一文件的并发重试合并;普通校时保留仍有效的签名。新增公共 FileImage,已加载图片在分钟检查、签名续期和恢复前台时保持当前画面;未加载的懒图片继续更新有效地址,放大预览在打开或切换图片时取有效签名。业务引用改变时正常加载新图。

  • 后台定向回归 19 项通过,包含真实组件 setup、模板与 Vue 生命周期测试;模拟停留超过 30 分钟、恢复前台、失败重试、预览和懒加载。
  • 新组件与文件工具的定向 vue-tsc 检查通过;后台生产构建通过。
  • 已登录商户页实测:原先已加载的 24 张图片跨分钟检查后地址不变,其余慢图片随后加载完成,共 30 张图片成功、无加载占位;放大预览成功且请求带签名。

浏览器直接打开文件地址限制补充

2026-10-29 新增默认开启的 block-browser-navigation。严格模式下,携带浏览器页面导航 Fetch Metadata 的文件请求,即使签名有效也返回 403;页面图片、音视频、fetch/XHR 下载和无浏览器 Metadata 的原生/服务请求继续按原签名规则处理。成功响应 Vary 区分导航与子资源请求;拒绝响应 no-store。观察模式仅记录 browser_navigation。

  • Java 文件访问/读取请求级测试共 12 项通过,覆盖签名正确的导航、GET/HEAD 在读取存储前拒绝、图片与下载放行、原生包下载、Dart UA 不绕过导航限制、开关和观察模式。
  • 后台 20 项、H5 16 项、小程序 16 项定向测试通过。覆盖文件链接的页面内下载、H5 富文本与普通链接拦截、图片预览、本地文档打开及第三方链接保持原行为。
  • 后台生产、H5、微信小程序构建通过;后台文件组件/工具定向类型检查通过。
  • 配置生成器在临时目录验证:新配置含 block-browser-navigation: true,默认仍为 observe,没有修改实际运行环境配置。

这项补充尚未部署到文件服务器,不能把本地请求级测试表述为该公网地址已返回 403。上线时代理须保留 Sec-Fetch-Mode/Dest 和响应 Vary;用新签名或无旧缓存的窗口验证。后端无法收回浏览器已经取得的文件内容。旧浏览器可能不提供 Fetch Metadata,脚本也能模拟请求,因此这项规则不等于逐用户授权或防止已显示图片被保存。

最终调整:统一一套密钥与严格模式联调

用户明确要求所有环境使用同一套密钥,取代本文前面记录的开发/测试/生产独立密钥方案。沿用已配置的原生产密钥组,未生成新密钥;各 appId 仍使用各自固定密钥,service/package 密钥仍只存在服务端。三个前端统一使用 .env.local,Java 使用 .file-access/config.yml,Flutter 使用 .file-access/config.json。旧模式文件的签名字段已清理并备份,旧命令 --environment 不再切换密钥。

此前严格模式白图的真实原因是:浏览器按开发密钥签名,后端配置的是原生产密钥,实际响应为 invalid_signature。本次已通过本地 Nacos REST API 修正 application-dev.yml,保留 enforce、浏览器导航拦截和旧 Dart 兼容,公网基础地址对齐测试网关。Nacos 完整原配置保存在本机私有临时备份,发布时使用 MD5 比较避免覆盖并发编辑,生产服务配置未修改。

  • 使用真实页面中的同一个签名 URL 联调:页面图片请求 200、应用下载请求 200、浏览器导航请求 403,原因为 browser_navigation。
  • 实际后台重新加载后,首轮观察为 30 张图片中的 24 张加载成功,其余 6 张仍在慢请求中;随后浏览器自动化连接中断,未把该轮全部图片的最终状态作为已验证结果。签名已与统一密钥注册表一致。
  • 真实 Vite loadEnv 验证三个项目共 18 个模式组合,全部使用统一密钥;生成器迁移、重复执行、原密钥保留与错误配置拒绝共 3 项测试通过。
  • 后台生产、H5、微信小程序构建通过;Flutter 使用统一配置执行签名和安装包定向测试,8 项通过。

后续构建与配置准备以 README 中的统一路径为准。本文前面提到的分环境配置路径属于历史验证记录,不再作为当前操作指引。

上传身份与文件白名单补充

2026-10-29 在现有读取协议和同一套固定密钥上增加上传身份校验。普通、内部、七牛三个文件中心上传入口均接入,读取签名不能用于上传;上传签名绑定 POST、目标入口、appId、kid、时间和一次性 nonce。默认 5 分钟有效,严格模式通过 Redis 原子占用 nonce 防止重放;内部上传仅接受 service 身份,Dart UA 不作为上传凭据。

文件白名单覆盖图片、PDF、APK、Office、TXT/CSV、ZIP 和常用音视频;仅内部上传额外允许 LOG/GZ。服务层在存储前检查后缀、文件头及适用的容器结构、图片尺寸和静态 SVG 内容,并规范存储扩展名与 MIME。默认最大 200 MiB,SVG 最大 1 MiB,现有网关/Servlet 更低限制仍适用。观察模式只放宽签名准入,文件类型检查始终执行。格式识别不代表病毒或文档宏扫描。

验证 本次结果
Java 上传测试 17 项通过,覆盖跨语言向量、入口隔离、重复头、过期、Dart UA 不放行、40 个并发请求只有一次 nonce 成功、Redis/错误配置失败关闭、实际 Feign multipart 编码、MockMvc 存储前拒绝及观察模式白名单
文件格式 覆盖常见图片、PDF、文本、Office 容器、ZIP、APK;拒绝改后缀/伪造 MIME、路径穿越、恶意 SVG、外部实体及过深 XML;最新构建的实际 Flutter Release APK 通过格式校验
Java 相关回归 52 项通过,覆盖原文件读取、版本地址、日志导出、钱包导出、硬件导出;UPMS、wallet、hardware、integrated、canteen 及所需公共模块编译通过
后台 31 项通过,执行实际 Axios 拦截器、通用上传和富文本上传 setup;保留登录/租户头和 multipart 业务字段,失败文件不阻塞同批成功回填,签名不写进保存的富文本
H5、小程序 各 21 项通过,执行实际 uni.uploadFile 拦截器;覆盖上传向量、每次请求新 nonce、校时及第三方/导入路径不签名
Flutter 签名、上传、安装包和兼容模式定向回归 21 项通过、2 项条件跳过;本次修改的文件工具、HTTP 管理器和上传测试定向 analyze 无诊断
构建 后台生产、H5、微信小程序及 Android Release 均通过;Flutter 使用 FVM 3.27.4 和 .file-access/config.json,APK 约 101.9 MB
配置生成器 4 项通过,确认只有一套环境共用密钥、原密钥不变、默认上传策略补齐、已有定制白名单和大小限制不被覆盖
TypeScript 后台全量仍有原有 6 条解析错误,H5 仍有原有 4 条解析错误,小程序仍有原有 17 条 TS6504;后台上传相关组件及其依赖与 HEAD 基线比较均为 115 条既有诊断,无新增诊断,不能表述为全量类型检查通过
源码与密钥 五个项目 Git 候选文件未发现当前活动密钥,保留并发的日志导出、Flutter 调试兼容等改动;Diff 空白检查通过

日志保存在本机 /tmp/pj-upload-security-work/。本次上传改造没有发布到运行中的 Java 服务,也没有修改 Nacos 或更换密钥。发布后的真实 multipart 联调、微信真机和旧 Flutter 设备验证仍需执行;MockMvc、内存 Feign 和本地构建不能代替现场验收。

已有 enforce 环境先发布客户端及携带新公共依赖的 Java 调用服务,再发布 UPMS,避免仅更新 UPMS 后拒绝旧上传请求。当前旧 Flutter 取证/日志先经过硬件业务接口,再由 Java 调用文件中心,服务端补签后不要求这些旧设备立即升级。observe 会允许旧上传身份请求,切到 enforce 才真正拦截未签名上传;白名单在两种模式下均有效。发布配置与详细头协议见 README 的“上传身份与文件类型”。

普通位图后缀兼容补充

2026-10-29 复现商户 Logo 上传失败:用户提供的 .png 文件实际为 RIFF/VP8 WebP,800×440、17,436 字节,独立图片解码验证正常。旧规则按 PNG 文件头判断,因此返回 415。

现对声明为 JPG/JPEG/PNG/GIF/BMP/WebP 的文件先识别实际位图格式,检查实际格式也在白名单后再执行对应内容校验,保存时使用实际扩展名和 MIME。原图片字节不变;限制白名单仍生效,其他文件类别不通过这个规则自动转为图片。后台单图、多图和七牛图片上传默认加入 WebP,并让失败的 Promise 交给 Element Plus 统一处理,避免失败后继续触发成功提示。

  • Java 32 项定向测试通过,含这张原图、公开合成 WebP、严格白名单、脚本/文档不自动转为图片、三个上传入口的 .webp 存储结果、原读取鉴权及安装包回归。
  • 后台 30 项定向测试通过,含三个图片组件的 WebP 选择、错误提示及原始返回地址保存。
  • 后台生产构建通过;上传相关组件及其依赖的 TypeScript 诊断由既有 115 条减为 114 条,去掉类型展开差异后无新增诊断。
  • 实际图只通过本机可选测试参数读取,没有作为仓库测试素材提交。验证日志在 /tmp/pj-upload-image-format-work/。

后端识别逻辑需要发布或重启更新后的 UPMS 才会生效;本次未更改运行环境、密钥或 Nacos。

旧 mobile H5 图片免签来源补充

2026-10-29 按用户要求,仅修改后端,为不能更新的 https://www.pjzhhq.cn/mobile/ H5 提供图片兼容。新增 file-access.public-image-referer-prefixes,默认空列表。请求 Referer 的协议、主机、有效端口和路径前缀全部匹配时,GET/HEAD 常见图片可免签,包括没有 appId 的旧请求;匹配来源携带的旧签名不再影响图片读取。Hash 路由不参与匹配。

  • 配置整体省略 clients.h5 可通过新版本校验;配置了空 H5 密钥仍拒绝启动,其他五类必要客户端维持严格要求。仅移除 H5 密钥不会自动放行任意 H5 请求。
  • 重复参数、非法文件名、浏览器页面导航仍先拒绝;其他来源、PDF/APK/日志/压缩包和上传入口不走该例外。
  • 已覆盖域名后缀伪造、userinfo、错误协议/端口、相似路径、路径穿越、重复 Referer、开关关闭、GET/HEAD、私有缓存和 Vary: Referer。
  • Java 跨模块 78 项回归通过,0 失败/跳过;配置生成器 4 项通过,确认默认不开启、已有前缀不被重写。
  • 实际检查线上 /mobile/ 返回 200,未发现禁用 Referer 的响应头或 HTML meta;这不代替后端部署后的真机验收。

精确前缀已加入本机被忽略的生成配置,其余配置和密钥保留。生产 Nacos 和线上服务未修改;需要部署新 UPMS/公共依赖并加载来源白名单,H5 无需重新发布。Referer 可被脚本模拟,此功能为应用级来源兼容。