代表性面试主题

后端面试:HTTP 425 Too Early 如何保护可重放请求

后端困难
Offer.cc 编辑团队发布 更新

题干

当 API 收到可能来自 TLS 早期数据的写请求时,什么时候返回 HTTP 425 Too Early?客户端、网关和服务端如何协作,才能避免重放副作用?

题干与适用场景

你负责支付、改密或创建订单 API。入口启用了 TLS 1.3 0-RTT,网关可能把带有 Early-Data: 1 的请求转发给服务。请说明何时返回 425、何时继续处理,以及客户端如何在握手完成后重试。题目适合后端、平台和 API 基础设施岗位。

RFC 8470 定义 425 是服务器不愿冒险处理可能被重放的请求;它不是“服务器太忙”的通用重试码。下面假设网关能保留 Early-Data 信号,服务能区分只读请求和有副作用请求。

面试官考察点

  • 能否把 0-RTT 的性能收益与重放风险分开,而不是把所有 4xx 都当作客户端输入错误。
  • 能否沿着客户端、网关、应用三段数据流说明谁负责等待、重试和幂等。
  • 能否指出 RFC 对 425 的发出条件、不可缓存性和重试时机,并把它转成可测试的 API 策略。

普通回答只背出“425 是 Too Early”。强回答会按副作用、Early-Data 标记、幂等键和重试窗口做决策,并说明错误配置会如何导致重复扣款。

回答前需要澄清的问题

  1. 请求是否带 Early-Data: 1,或入口是否能证明它来自早期数据?RFC 建议服务没有这些信号时不要随意返回 425。
  2. 操作是否有外部副作用?读取订单可以继续;扣款、改密和发券需要等待握手,或先建立严格的幂等状态。
  3. 网关是否会自动重试?如果会,必须确认它在握手完成后重试,并且不会让应用和网关各自重试一次。
  4. 客户端是否支持幂等键和重试上限?没有这些能力时,宁可关闭该类请求的 0-RTT,也不要把 425 当作无限重试许可。

30 秒回答框架

“我先确认请求是否来自带 Early-Data: 1 的 TLS 早期数据,再判断它是否有副作用。只读请求可以继续;支付、改密等写请求先返回 425 或在网关等待握手。客户端只在握手完成后重试,服务端用幂等键和唯一约束防止重复执行。网关要保留信号、避免双重重试,并监控 425 比例、重试成功率和重复副作用。若链路无法证明这些条件,我会关闭该端点的 0-RTT。”

分步骤深入解答

1. 先识别风险信号

RFC 8470 要求中间层在收到早期数据时保留 Early-Data 语义;服务端只有在请求可能被重放时才应考虑 425。没有信号却返回 425,会把普通网络失败误报成安全拒绝。

2. 按副作用分类

把端点分成只读、可安全重复和不可安全重复三类。GET /orders/123 通常属于只读;生成一次性优惠券、扣款和修改密码属于不可安全重复。后者应等待完整握手,或由应用在持久化层先记录幂等键。

3. 规定单一重试责任

客户端收到 425 后,必须等待 TLS 握手完成,再发送同一请求;重试不能再次走早期数据。网关可以代重试,但要把责任写入契约,避免网关和 SDK 同时重试。设置指数退避、次数上限和可观测的重试原因。

4. 把幂等作为第二道防线

对写请求要求客户端生成幂等键。服务端用租户、端点和键建立唯一记录,状态至少区分处理中、成功和可重试失败;相同键重复到达时返回原结果或明确的处理中状态。幂等键不能替代 425,因为首次处理仍可能在数据库提交后于响应前被重放。

5. 处理网关与多实例一致性

所有实例都要使用同一 Early-Data 策略。网关若不确定上游是否理解该信号,应等待握手或直接拒绝;不能把早期写请求静默转给只识别普通 HTTP 的服务。日志记录请求 ID、Early-Data、425 原因和幂等键哈希,避免记录支付内容。

6. 验证失败路径

测试带与不带 Early-Data: 1 的请求、握手完成后的首次重试、网关自动重试、客户端超时后再次提交,以及两个实例并发处理同一幂等键。断言不可重复副作用只发生一次,且 425 不被缓存。

替代方案是完全关闭 0-RTT:安全边界简单,代价是增加首个请求的握手延迟。若端点数量少、延迟收益不重要,关闭往往比维护跨层策略更稳妥。

高质量示范回答

“我不会把 425 当成普通限流错误。先看请求是否带 Early-Data: 1,再看操作是否有副作用。订单查询可以继续;支付和改密在网关等待握手,或由服务返回 425。客户端收到后只能在握手完成后重试一次,SDK 的上限和退避要写入契约。服务端还要求幂等键,数据库用租户加键做唯一约束,并保存处理中和最终结果,防止响应丢失导致重复扣款。所有网关和实例采用同一规则,监控 425、重试成功率和重复执行告警。如果链路无法传递 Early-Data,我会关闭写端点的 0-RTT,而不是猜测请求是否安全。”

常见错误

  • 把 425 当作 429 的替代品 → 触发原因和客户端动作不同 → 只在早期数据重放风险成立时使用 425。
  • 所有请求都返回 425 → 读请求被无谓阻塞,客户端可能无限重试 → 按副作用分类并设置上限。
  • 重试仍走 0-RTT → 重放窗口没有关闭 → 明确要求握手完成后再发。
  • 只在应用内做幂等 → 网关或另一个实例可能先执行副作用 → 在入口、应用和持久化层统一策略。
  • 记录完整支付体排查重放 → 日志暴露敏感数据 → 只记录请求 ID、原因和脱敏后的幂等键。

追问及应对

如果网关移除了 Early-Data 头,你如何处理?

我会把它视为能力缺失:网关要么等待握手后再转发,要么拒绝早期请求。应用不能从普通请求中推断它曾经使用 0-RTT;先修复信号传递,再启用写端点。

如果客户端在收到 425 前已经超时并再次提交呢?

用同一个幂等键接住两次提交,第二次读取第一笔的处理中或最终状态。对没有幂等键的危险写请求,返回可诊断错误并进入人工或补偿流程,不自动猜测是否已扣款。

如果重试成功率很高但延迟 SLO 变差,你会怎么取舍?

按端点和用户代理分段比较 425 率、p95 首次成功延迟、重复副作用和业务成功率。若写请求收益不足以抵消延迟,我会只保留安全读请求的 0-RTT,或直接关闭该类端点。

公开来源

同类题目