题干与适用场景
一个支付合作方要求验证每个 HTTP 请求的发送方和内容完整性。请基于 RFC 9421 设计签名与验签流程,并说明覆盖哪些组件、如何防重放、轮换密钥以及处理代理改写。
RFC 9421 将签名输入、覆盖的 HTTP 组件和 created、expires、nonce 等参数分开定义。题目考察能否把“谁签的”和“哪些字节被签”落实为可互操作的协议,而不是简单说“给 body 做 SHA-256”。
面试官考察点
重点包括:覆盖组件是否足以绑定请求语义;客户端和服务端如何规范化签名基线;时间、nonce 和密钥 ID 如何防重放;代理、重试、压缩和多租户如何影响验签;以及密钥发布、吊销和监控是否可运维。
30 秒回答框架
“我先定义协议要求覆盖的方法、目标路径、authority、关键业务头和 Content-Digest,并固定签名算法、参数顺序和时钟容差。服务端先解析 Signature-Input,按相同规则重建签名基线,再查密钥、验证签名、检查 created、expires 和一次性 nonce,最后才进入业务。密钥用版本化 key ID 支持双写验签和吊销;代理只能改写未覆盖字段,否则验签失败。所有失败原因做分类指标,但不把详细密钥信息返回给调用方。”
分步骤深入解答
第一步:明确要证明的属性
签名可以证明持钥方对特定 HTTP 组件做过签名,并帮助检测传输中的改写;它不替代 TLS、授权、输入校验或业务幂等。先写清楚需要认证请求、完整性,还是不可否认性。
第二步:选择覆盖组件
至少覆盖方法、目标路径和 authority;按业务再覆盖 content-digest、幂等键、租户 ID 或关键业务头。不要只签一个可被替换的业务字段,也不要盲目覆盖会被合法代理重写的头。组件清单必须成为版本化协议。
第三步:规范化签名基线
客户端和服务端必须使用 RFC 9421 规定的组件标识、顺序、参数编码和派生组件规则。服务端不能直接拼接原始 Header 字符串猜测结果;应从解析后的请求和 Signature-Input 重建同一基线,并拒绝未知或重复的关键组件。
第四步:保护请求内容
对有 body 的请求生成 Content-Digest,再把该字段纳入签名。验签前先按收到的字节计算摘要,确保压缩、转码或 JSON 重排不会静默改变语义。若代理会解压或重编码,协议必须明确签名发生在修改前还是修改后。
第五步:设计时间与防重放
要求 created,必要时要求 expires 和唯一 nonce。服务端校验时间窗口和允许的时钟偏差,并在租户范围内短期记录已用 nonce;重试相同业务请求应依赖幂等键,而不是无限放宽签名有效期。时间检查通过不代表业务请求可以重复执行。
第六步:实现密钥发现与轮换
Signature-Input 中携带 key ID,服务端从受控目录或 JWKS 类发布端点读取公钥,并缓存带版本的结果。轮换时先发布新公钥,再允许新旧 key 双验签,确认旧流量耗尽后吊销旧 key。私钥只在签名端使用,日志不得记录密钥材料或完整签名基线。
第七步:处理代理与重试边界
明确哪些层可以添加追踪头、重试请求或改变 authority。被覆盖组件发生任何不被协议允许的改写都应验签失败;代理重试时不能复制一次性 nonce,也不能把一个签名转发到不同目标。服务端在验签前不要执行写入或昂贵业务逻辑。
第八步:观测和故障处理
记录 key ID、签名版本、失败类别、时钟偏差、nonce 冲突、组件缺失和代理来源,使用请求 ID 关联日志。对合作方提供可操作的错误码,例如过期、未知 key、摘要不匹配;不要返回可帮助攻击者试错的详细密钥或基线内容。
设计取舍与边界
非对称签名还是共享密钥
非对称密钥便于多方验签和独立吊销,代价是签名与密钥基础设施更复杂。共享 HMAC 成本较低,但验证方也能伪造请求,适合双方完全互信且边界清晰的场景。
覆盖范围还是代理兼容
覆盖越多,完整性约束越强;覆盖会被网关合法修改的字段则会造成误失败。把代理契约写入协议,必要时由边界代理重新签名,而不是静默放宽验签。
严格时间窗口还是离线重试
短窗口降低重放风险,却要求时钟同步和快速重试。离线客户端应申请新的签名,不应复用长期有效签名;服务端可按合作方设置受控容差。
失败演练与演进计划
代理修改路径
在测试代理中改写路径或 authority,确认验签失败且没有进入业务;允许的追踪头改写不应影响签名。
重放旧请求
重复发送相同签名和 nonce,验证第二次被拒绝;使用新的 nonce 但相同幂等键时,验证业务层按幂等规则处理。
密钥轮换窗口
同时发布新旧 key,验证新旧签名均能按策略通过;撤销旧 key 后旧签名应失败,且不会因缓存无限存活。
常见误区与追问
误区一:只签 body 的哈希
追问:攻击者能否把签名复制到另一个路径或方法?应覆盖目标组件并绑定请求语义。
误区二:认为 HTTPS 已经足够
追问:终止 TLS 的代理之后如何证明原始发送方和内容没有被内部层改写?
误区三:忽略 nonce 和时间
追问:捕获的合法签名能否在有效期内重复扣款?应结合时间窗口、nonce 和幂等键。
误区四:轮换时立刻删除旧 key
追问:在途请求和多区域缓存如何过渡?应先双验签,再吊销旧 key。
误区五:把验签错误直接返回基线细节
追问:如何让合作方排障又不泄露签名输入、密钥或内部代理信息?
延伸追问与参考答案
Content-Digest 为什么还要放进签名?
摘要证明 body 字节,签名把摘要与方法、目标和其他组件绑定,避免攻击者把一个合法摘要搬到另一请求。
代理改写后应该重新签名吗?
如果改写字段属于下游协议的一部分,边界代理应以自己的身份重新签名;否则应保持被覆盖组件不变并让原签名继续验证。
如何区分认证和授权?
验签确认持钥方签过请求,授权仍需检查租户、账户、金额、幂等键和业务状态,不能由签名单独决定是否执行。