Spring Boot 通过 @RestController 可以轻松创建 webhook 端点,但 Spring Security CSRF 和 JSON 转换器可能在 handler 运行前阻止或修改 payload。本地测试应验证 byte[] 原始访问、webhook 路径的 CSRF 豁免以及对未改动字节的签名验证。
创建 @RestController webhook 端点
保持 webhook 控制器精简。接收原始字节、验证签名后再反序列化:
@RestController
@RequestMapping("/webhooks")
public class StripeWebhookController {
@PostMapping("/stripe")
public ResponseEntity handle(
@RequestBody byte[] payload,
@RequestHeader("Stripe-Signature") String signature) {
if (!verifier.isValid(payload, signature)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
JsonNode event = objectMapper.readTree(payload);
handler.process(event);
return ResponseEntity.ok().build();
}
}
@RequestBody byte[] 保留 Tomcat 收到的精确 payload — 适合提供商对原始 JSON 签名的场景。
通过 HttpServletRequest 读取原始 body
或者直接从 servlet 请求读取:
byte[] body = request.getInputStream().readAllBytes();
String sig = request.getHeader("Stripe-Signature");
不要先绑定到 POJO 或 Map — Jackson 重新序列化会破坏 HMAC。参见 签名验证指南。
仅对 webhook 路径禁用 CSRF
Spring Security 默认启用 CSRF。没有 server-to-server 例外时 webhook POST 会返回 403:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.ignoringRequestMatchers("/webhooks/**"));
return http.build();
}
}
将 ignore 限制为 /webhooks/** — 切勿全局禁用 CSRF。
在业务逻辑前验证签名头
读取提供商请求头,从原始 byte 数组计算 digest,使用恒定时间比较。失败返回 401,成功快速返回 200 以减少重试。
本地隧道工作流
- 启动:
./mvnw spring-boot:run端口 8080。 - 暴露 Tomcat:
npx portpreview 8080。 - 将隧道 URL + 路径粘贴到控制台。
- 发送测试事件并查看日志。
- 重放相同 event ID 确认幂等 — 见 重试模式。
常见陷阱
过滤器顺序与 body 消耗
在控制器之前读取 stream 的过滤器会留下空流。如需多层检查 body,使用 ContentCachingRequestWrapper。
全局 @RequestBody 绑定
过早解析 JSON 的 @ControllerAdvice 可能改变字节。用 byte[] 隔离 webhook 端点。
慢 handler 触发重试
快速确认、异步处理、按 event ID 去重。
延伸阅读
基础阅读localhost 隧道基础和本地 webhook 调试。签名机制见签名验证指南。防重复处理见重试与幂等性模式。免费开始使用 PortPreview。
![embedded Tomcat 上的 Spring Boot RestController 通过本地隧道接收原始 byte[] payload 并验证 webhook 签名头。](/_next/image?url=%2Fimages%2Farticles%2Fspring-boot-webhook-local-testing%2Fcover.png&w=3840&q=75)