kra-oa/docs/audit/parity-crosscut.md

85 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# GVA 横切接口对照审计
基准:`C:/Users/Yvan/AppData/Local/Temp/gva-parity-02f3783`commit `02f37833255e0e339c3d69199cb5a468f17de9fc`2026-08-06
范围:只审查已迁移后台接口共用的 HTTP/router/middleware 行为未迁移的代码生成、AI 等模块不计入缺口。
`GET /sysError/getSysErrorSolution` 调用 LLMAuto按要求排除。
## 结论摘要
| 项 | 结论 | 严重度 |
|---|---|---|
| 路由实际集合 | 业务路由方法和路径基本对齐;本地 `engine.Routes()` 不含动态本地静态文件,而 GVA `StaticFS` 会注册静态 `GET`/`HEAD` wildcard | P1API sync/route-table 可见集合) |
| 静态文件 URL | GVA 以 `local.store-path` 注册静态路由;本地 NoRoute 以 `local.path-prefix` 动态处理。默认值相同,配置分离时行为不同 | P1配置不一致时 |
| Swagger `basePath` | GVA 生成文档的 `docs.SwaggerInfo.BasePath` 在无前缀时为空字符串;本地动态文档把空前缀规范化为 `/`。带 `/admin` 前缀时两端都输出 `/admin` | P3严格文档/客户端比较) |
| 统一响应/状态码 | 成功/业务失败均 HTTP 200 + `{code,data,msg}`;未登录 401强制改密 409panic 500限流 200和 GVA 一致 | 对齐 |
| 请求绑定错误 | `ShouldBindJSON/Query` 错误均转换为 HTTP 200、code 7、data `{}`;与 GVA 一致 | 对齐 |
| JWT/刷新/多点登录 | 黑名单、临近过期刷新、cookie/`new-token` 头、多点登录旧 token 作废逻辑对齐;本地用持久化黑名单并 singleflight属于实现差异 | P2实现/故障语义需回归) |
| MustChangePwd | 允许 `/user/changePassword`、`/user/getUserInfo`、`/jwt/jsonInBlacklist`,其余 409/code 10001对齐 | 对齐 |
| Casbin | 本地每次从 `casbin_rule` 读取并直接 EnforceGVA 使用内存 enforcer。`freshCasbin` 本地恒成功GVA `LoadPolicy` 失败会返回 code 7 | P1 |
| DataScope | 顺序均为 JWT -> MustChangePwd -> Casbin -> DataScope本地额外注入 actor/request metadata不改变已迁移接口授权规则 | 对齐(增强) |
| CORS | GVA 基准的 `Cors/CorsByRules``router.go` 中均为注释、默认未安装;本地无条件安装并按 runtime 执行 allow-all/whitelist/strict。默认 `whitelist` 空配置时差异不显著,但 strict/allow-all 会改变可达性与响应头 | P1启用配置时行为不兼容 |
| 登录/验证码限流 | GVA 仅在 `POST /base/login`、`POST /base/captcha` 路由挂限流;本地是全局 middleware按 URL 后缀匹配,未匹配路由或其他方法也可能计数。超限 HTTP 200/code 7、缓存异常 fail-open另有 key 前缀 `KRA_SecLimit` vs `GVA_SecLimit` | P2作用面与跨版本缓存兼容 |
| request/trace metadata | RequestMeta 最先执行,`X-Request-Id`、`X-Trace-Id`、W3C `traceparent` 的校验/回写顺序一致 | 对齐 |
| access log | 单一 body/response capture、脱敏、1 MiB response cap、route/status/bytes/identity/trace 字段一致;版本头不同 | P2 |
| operation audit | 已迁移的 GVA `OperationRecord` 分组路由均被本地 `operationRoutes` 覆盖AI `getSysErrorSolution` 排除 | 对齐(需防漏项测试) |
| error audit | panic/错误级日志走单一 error sink本地响应 envelope 的未预期业务失败由 `ErrorAudit` 转为 error logGVA 由 logger core 捕捉 error level | 对齐(实现不同) |
| panic recovery | broken-pipe 仅记录并 abort普通 panic 返回 500对齐 | 对齐 |
| API sync/init seed | 本地初始化/同步使用 `engine.Routes()`;本地会去掉 router-prefixGVA 基准直接使用带 prefix 的 `GVA_ROUTERS`。静态 routes 缺失叠加影响 sync/seed | P1自定义 prefix |
## 逐项核验
### 1. Router 与静态文件
GVA `server/initialize/router.go` 的顺序为 `RequestMeta -> GinRecovery -> AccessLog -> UploadResponseHeaders -> StaticFS -> Swagger -> Public/Private`Private 依次安装 `JWTAuth -> MustChangePwdGuard -> CasbinHandler -> DataScope`。本地 `internal/server/gin.go``RequestMeta -> Recovery -> AccessLog -> CORS -> ErrorAudit -> SecurityRateLimit`,再建立 Public/PrivatePrivate 依次为 `Auth -> MustChangePassword -> AccessControl -> OperationAudit`。已迁移业务 handler 的路径/HTTP method 与路由契约测试一致。
本地没有调用 `StaticFS`,而是在 `NoRoute` 中用 `serveLocalStorage``local.path-prefix` 动态匹配;因此动态静态文件不会出现在 `engine.Routes()``/api/syncApi` 输入中。GVA `StaticFS(global.GVA_CONFIG.Local.StorePath, ...)` 会注册静态 wildcard 的 GET 和 HEAD并进入 `GVA_ROUTERS`。默认配置两者都为 `uploads/file`,但如果 `store-path != path-prefix`,两端暴露的 URL 集合不同。基准 GVA 的预生成 Swagger 文档也没有 `/uploads/file/*filepath`,而 Kra 的动态 Swagger 同样不会把 NoRoute fallback 当作文档路由,因此 Swagger 本身不单独计为已确认差异。静态响应头(`nosniff`,非安全媒体 `Content-Disposition: attachment`)的效果一致;本地额外拒绝目录。
Swagger 文档的根路径还有一个低等级差异:基准 `server/docs/docs.go``BasePath` 默认值为空字符串,`server/initialize/router.go` 仅在配置有前缀时写入;本地 `internal/server/swagger.go:59-63` 将空前缀强制写成 `"/"`。这通常不会改变浏览器请求,但会影响严格 JSON 快照或按 `basePath` 拼接 URL 的客户端。
**测试建议**:构造临时目录和 `store-path != path-prefix`;断言 GVA 期望的 `GET`/`HEAD /<store-path>/*filepath` 是否进入 route table 与 sync diff且确认两端 Swagger 文档的静态路由口径;断言本地动态静态响应仍支持 GET/HEAD、目录 404/403、头部一致。
### 2. 统一响应与绑定错误
本地 `internal/server/httpx/response.go` 与 GVA `server/model/common/response/response.go` 均定义 `CodeSuccess=0`、`CodeError=7`、`CodePasswordChangeRequired=10001`,正常/业务失败均 `HTTP 200` envelope。`NoAuth` 是 `401``data:null`MustChangePwd 是 `409`Recovery 普通 panic 是 `500` 空响应CORS 拒绝 `403`;限流是 `200` + code 7。handler 统一在 `ShouldBindJSON/ShouldBindQuery` 失败时写 code 7未引入 Gin 默认 400。
**测试建议**:每类 endpoint 发送 malformed JSON、类型错误 query、缺字段 JSON比较 status、code、data 和 msg覆盖 401/409/403/429-like实际 200/500。
### 3. JWT、刷新和多点登录
GVA `middleware/jwt.go` 先检查 blacklist再解析 token临近过期时签发新 token并回写 `new-token`、`new-expires-at` 和 cookie。登录在多点模式下将旧 Redis JWT 加入 blacklist再写入新 active token。Kra `middleware/auth.go` 通过 `AuthService.AuthenticateToken` 完成同一顺序,并在刷新时回写相同 headers/cookie登录 usecase 的 `RotateActiveToken` 在多点模式作废旧 token刷新则传空 old token 保持旧 token 不被提前作废。Kra 用 `jwt_blacklists` 持久化查询并对同一 token 使用 singleflightGVA 使用无错误返回的缓存 blacklist因此数据库故障时 Kra 会把所有 token 映射为“异地登陆或令牌失效”并拒绝请求,而 GVA 仍可能继续 JWT 解析。这是故障语义差异,需在部署策略和回归测试中明确。
### 4. MustChangePwd、Casbin、DataScope
两端允许列表完全一致:`/user/changePassword`、`/user/getUserInfo`、`/jwt/jsonInBlacklist`。Kra `AccessControl` 会去掉 router-prefix 后按 `authority_id + path + method` 授权,并在同一 request context 注入 DataScope/ActorGVA `CasbinHandler` 做相同 prefix trim随后 `DataScope` 注入数据域身份。Kra 的授权实现每请求读取数据库并构造 Casbin modelGVA 使用全局 enforcer 缓存;所以 Kra 写策略后即时可见,而 GVA 依赖 `FreshCasbin` 或写路径主动 reload。
`GET /api/freshCasbin` 是明确缺口GVA 调 `e.LoadPolicy()`,失败返回 HTTP 200/code 7/“刷新失败”Kra handler 直接 HTTP 200/code 0/“刷新成功”,且不会暴露加载失败。即便 Kra 当前无内存 enforcer该接口的可观察错误语义仍不一致。
### 5. CORS、限流与 metadata
CORS 代码实现本身的 allow-all、strict-whitelist、匹配 origin、OPTIONS 204、`GET /health` 例外和头部名称与 GVA helper 相近,但基准 `server/initialize/router.go``Cors/CorsByRules` 两行都保留为注释,默认并未安装该 middlewareKra 在 `NewGinEngine` 中无条件安装并每次读取 runtime。因此在 `strict-whitelist``allow-all` 配置下Kra 会拒绝/放行并添加 CORS 头,而基准仍按普通路由处理。若部署契约要求启用 CORS应在基准与本地明确同样的安装开关否则应把该差异视为 P1。
登录/验证码限流的窗口、超限响应、缓存异常 fail-open 一致但作用面不完全相同GVA 只在 `POST /base/login`、`POST /base/captcha` 路由挂 `SecurityLimit()`Kra 则在全局 middleware 中按 URL 后缀匹配,未匹配路由或其他方法也可能读取/递增计数。两端 key 前缀仍不同Kra `KRA_SecLimit`GVA `GVA_SecLimit`。滚动部署共享缓存时会导致计数不共享,建议统一前缀或明确迁移策略。
RequestMeta 两端都位于最前,生成/透传 request id、trace id、span id并按合法 `traceparent` 优先、`X-Trace-Id` 回退;响应头和 context 字段一致。AccessLog 都只读一次 body、包装一次 writer、脱敏敏感 JSON/header、响应捕获上限 1 MiB并记录 route/status/latency/bytes/identity/request/trace metadata。
版本响应头是第二个明确可见差异GVA 写 `X-Gva-Version`Kra 写 `X-Kra-Version`。如果前端、网关或探针按 GVA header 读取版本,这会造成兼容性缺口;建议双写过渡或在契约中明确新 header。
### 6. Operation/Error audit 与 recovery
GVA 通过各资源 router group 的 `Use(middleware.OperationRecord())` 选择写操作Kra 在全局 Private 后通过 `recordsOperation` 白名单按 method+route suffix 选择,已迁移的 user/api/authority/menu/department/position/dictionary/params/security/system/token/version/export/error/login-log/data-access/timed-task/info/email 路径均有覆盖。`sysError/getSysErrorSolution` 属 LLMAuto按要求不纳入。
两端 operation audit 均复用 AccessLog 的 request/response captureGET 将 query 转为 JSONmultipart 写 `[文件]`,下载响应超限写 `[超出记录长度]`,并记录 request/trace/device metadata。GVA logger core 和 Kra logging error sink 都会将 Error 级别日志落入 `sys_error`Kra `ErrorAudit` 额外把未预期的 code 7 envelope 提升为 error log并排除已知客户端失败、sysError/logViewer 自记录,避免重复。
Recovery 对 broken pipe 只记录并 abort普通 panic 记录请求/stack 并返回 500行为一致。
## 修复/验证计划(按优先级)
1. **P1 路由集合与 API sync/seed**:决定静态文件是否应纳入 API/route-table 同步。若要严格 GVA parity应注册与 GVA 同形的 `GET`/`HEAD` static wildcard并以 `store-path` 作为 URL若保留动态 NoRoute应在 sync/seed 明确排除并补文档/测试。额外覆盖非空 router-prefix并保留两端 Swagger 对静态路由的已验证口径。
2. **P1 FreshCasbin**:让 `freshCasbin` 调用实际策略刷新或至少执行数据库/缓存可用性检查;失败返回 GVA 相同的 code 7 envelope。增加故障注入测试。
3. **P1 router-prefix sync**:明确数据库中的 API path 是带前缀还是无前缀;当前 Kra `NormalizeRoutePath` 去前缀,而 GVA `GVA_ROUTERS` 直接保留前缀。选择兼容策略并覆盖初始化、`syncApi`、Casbin authorize 三条链路。
4. **P1 CORS 启用策略**:让 CORS 是否安装由与 GVA 相同的部署开关决定,或把 Kra 的无条件 CORS 明确记录为专有契约;覆盖 strict/allow-all/OPTIONS。
5. **P2 版本头**:兼容期双写 `X-Gva-Version``X-Kra-Version`,并增加 header contract test。
6. **P2 限流与黑名单故障**:收窄限流到两个 POST route统一/双读 `GVA_SecLimit` key并明确黑名单存储故障的 fail-open/fail-closed 行为。
7. **P2 横切回归**:运行 malformed binding、JWT expired/refresh/multipoint/**blacklist-store failure**、MustChangePwd allow-list、Casbin deny/fresh failure、DataScope ownership、CORS strict/OPTIONS、rate-limit fail-open、GET/HEAD static、operation/error audit、panic/broken-pipe 的 HTTP 集成测试。
8. **P3 文档兼容**:决定是否把无前缀 Swagger `basePath``"/"` 调整为空字符串,并固定 snapshot 规则。