第16章 把实验迁移到 Go、Java 与网关体系
先迁移契约,再迁移框架
Go 的标准 HTTP handler 让信任边界直接可见,Java 的成熟安全框架可以承担大量协议细节。选择框架不是核心差异,关键是保持相同契约:固定可信 issuer,验证目标 audience 与算法,区分主体与 actor,获取当前授权状态,在业务服务内检查租户、对象、动作和审批。
Go 生产实现可以使用成熟 OIDC discovery 库、JWT 库与身份平台 SDK,避免自己维护完整协议栈。Java 可使用 Spring Security Resource Server 或相应平台组件验证访问令牌,再把已验证主体映射为业务上下文。不要把未经验证的 header 直接变成 SecurityContext,也不要在控制器里拿一个解码后的 JWT 就执行写入。
身份上下文应作为不可由业务参数覆盖的独立结构传递。函数参数可以同时接收 AuthContext 和 RefundCommand,前者来自认证链路,后者来自工具输入。不要把 tenant、user 和 command 混进一个可被 JSON 反序列化任意覆盖的大对象。代码评审时,这种类型分离能显著降低误用概率。
网关能承担什么
Apache ShenYu 或其他 API 网关可以处理路由、限流、基础认证、协议转换与审计关联。它适合提前拒绝非法来源和明显无效令牌,也能帮助稳定请求 ID。最终对象授权仍应在业务服务,尤其是退款状态、租户归属、金额上限与审批消费需要靠近数据库事务。
失败案例是业务服务只允许网关 IP,因此完全移除验签和租户校验。后来一个后台运维任务直接连到服务,或者网关新增了一个不带认证的路径,业务边界立即消失。网络限制是有用的附加层,却不能解释“当前主体为什么可以操作这张订单”。服务间认证与对象授权都应有清晰位置。
若网关使用内部签名头传递身份,需要定义签名范围、时限、重放保护、目标服务、密钥轮换和可信入口清理。不能只增加 X-User,然后假设所有到达业务服务的头都来自网关。通常使用面向业务服务的短期令牌或成熟服务身份方案,更容易获得明确的验证契约。
策略引擎与业务事务的连接
策略引擎适合表达岗位、租户、环境和风险条件,但对象状态与审批消费仍需要一致性。可以先向策略引擎请求一个带版本的决定,再在事务中验证相关版本与条件;也可以把关键不变量保留在业务代码。外部决定不能成为一个永不过期的 allow=true。
迁移时建议先选择低风险只读工具,再引入单对象审批写入,最后扩展批量与异步任务。每一步都复用同一组攻击样本,并增加真实数据库和身份平台测试。不要一开始就给 Agent 一个管理员万能工具,再计划后续慢慢拆权限。
可运行回归与练习
把本书 go test -race ./lab 作为契约参考。若重写成 Java,可以保留同样的 HTTP 黑盒样本:错误受众、跨租户、旧审批、撤权、轮换与审计脱敏。框架单元测试通过不代表整条链路通过,至少需要真实签发方和真实业务服务的集成测试。
练习:为现有一个 Java service 标出认证过滤器、身份映射、对象读取、授权判断、审批消费与数据库提交的位置。若它们分散在六个拦截器中,写出明确执行顺序和失败行为。再说明哪些规则可以在网关预检,哪些必须由 service 保留,给每项一个具体攻击样本。
多语言团队如何保持一致
若 Go 代理与 Java 业务服务分别实现参数摘要,字段排序、空值、整数范围和字符串规范化可能不同。审批绑定失败并不一定是攻击,也可能是编码差异。应定义版本化规范,保存跨语言测试向量,并让两个实现共同运行,而不是靠“都是 JSON”推定一致。
身份字段映射也需要同样约束。一个库可能把 audience 解析成列表,另一个只处理单字符串;一个框架默认验证 issuer,另一个需要显式添加。迁移验收应直接使用真实令牌样本和错误样本,从 HTTP 入口观察结果,避免只对照配置文件名称。
业务团队还应为每个新增入口复用授权 service,确保后台任务、管理接口和批处理不会绕过核心规则。但复用不是把所有事实藏进静态工具函数:事务中需要的对象和版本应明确传入,授权失败应有结构化原因。清晰的代码边界比隐式拦截器链更容易被安全评审。