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

13 KiB
Raw Blame History

GVA 横切接口对照审计

基准:C:/Users/Yvan/AppData/Local/Temp/gva-parity-02f3783commit 02f37833255e0e339c3d69199cb5a468f17de9fc2026-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/CorsByRulesrouter.go 中均为注释、默认未安装;本地无条件安装并按 runtime 执行 allow-all/whitelist/strict。默认 whitelist 空配置时差异不显著,但 strict/allow-all 会改变可达性与响应头 P1启用配置时行为不兼容
登录/验证码限流 GVA 仅在 POST /base/loginPOST /base/captcha 路由挂限流;本地是全局 middleware按 URL 后缀匹配,未匹配路由或其他方法也可能计数。超限 HTTP 200/code 7、缓存异常 fail-open另有 key 前缀 KRA_SecLimit vs GVA_SecLimit P2作用面与跨版本缓存兼容
request/trace metadata RequestMeta 最先执行,X-Request-IdX-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/PrivatePrivate 依次安装 JWTAuth -> MustChangePwdGuard -> CasbinHandler -> DataScope。本地 internal/server/gin.goRequestMeta -> Recovery -> AccessLog -> CORS -> ErrorAudit -> SecurityRateLimit,再建立 Public/PrivatePrivate 依次为 Auth -> MustChangePassword -> AccessControl -> OperationAudit。已迁移业务 handler 的路径/HTTP method 与路由契约测试一致。

本地没有调用 StaticFS,而是在 NoRoute 中用 serveLocalStoragelocal.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.goBasePath 默认值为空字符串,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=0CodeError=7CodePasswordChangeRequired=10001,正常/业务失败均 HTTP 200 envelope。NoAuth401data:nullMustChangePwd 是 409Recovery 普通 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-tokennew-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.goCors/CorsByRules 两行都保留为注释,默认并未安装该 middlewareKra 在 NewGinEngine 中无条件安装并每次读取 runtime。因此在 strict-whitelistallow-all 配置下Kra 会拒绝/放行并添加 CORS 头,而基准仍按普通路由处理。若部署契约要求启用 CORS应在基准与本地明确同样的安装开关否则应把该差异视为 P1。

登录/验证码限流的窗口、超限响应、缓存异常 fail-open 一致但作用面不完全相同GVA 只在 POST /base/loginPOST /base/captcha 路由挂 SecurityLimit()Kra 则在全局 middleware 中按 URL 后缀匹配,未匹配路由或其他方法也可能读取/递增计数。两端 key 前缀仍不同Kra KRA_SecLimitGVA 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-VersionKra 写 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_errorKra 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-VersionX-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 规则。