บทความทั้งหมด
Spring Boot RestController บน embedded Tomcat ตรวจ header ลายเซ็น webhook จาก byte[] ดิบที่รับผ่าน tunnel ในเครื่อง
Spring BootJavawebhook debugginglocal testing

Spring Boot webhook: raw body, ยกเว้น CSRF และ header ลายเซ็น

Spring Boot ทำ webhook endpoint ด้วย @RestController ได้ง่าย แต่ Spring Security CSRF และ JSON converter อาจบล็อกหรือเปลี่ยน payload ก่อน handler ทดสอบในเครื่องต้องพิสูจน์การเข้าถึง byte[] ดิบ ยกเว้น CSRF บน path webhook และตรวจลายเซ็นบน byte ที่ไม่ถูกแตะ

สร้าง webhook endpoint @RestController

เก็บ controller ให้บาง รับ byte ดิบ ตรวจลายเซ็น แล้ว deserialize:

@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[] เก็บ payload ตรงที่ Tomcat รับ — เหมาะเมื่อ provider เซ็น JSON ดิบ

อ่าน raw body ด้วย HttpServletRequest

ทางเลือก: อ่านจาก servlet request โดยตรง:

byte[] body = request.getInputStream().readAllBytes();
String sig = request.getHeader("Stripe-Signature");

อย่า bind POJO หรือ Map ก่อน — Jackson reserialize ทำให้ HMAC พัง ดู คู่มือตรวจลายเซ็น

ปิด CSRF เฉพาะ path webhook

Spring Security เปิด CSRF โดยค่าเริ่มต้น POST webhook ล้ม 403 หากไม่มีข้อยกเว้น server-to-server:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.ignoringRequestMatchers("/webhooks/**"));
        return http.build();
    }
}

จำกัด ignore ที่ /webhooks/** — อย่าปิด CSRF ทั้งแอป

ตรวจ header ลายเซ็นก่อน business logic

อ่าน header จาก provider คำนวณ digest จาก byte array เปรียบเทียบแบบ constant-time 401 ถ้าผิด 200 เร็วเมื่อสำเร็จ

ขั้นตอน tunnel ในเครื่อง

  1. เริ่ม: ./mvnw spring-boot:run พอร์ต 8080
  2. เปิด Tomcat: npx portpreview 8080
  3. วาง URL tunnel + path ใน dashboard
  4. ส่ง event ทดสอบและดู log
  5. เล่น event ID เดิมเพื่อ idempotency — ดู รูปแบบ retry

กับดักที่พบบ่อย

ลำดับ filter และการ consume body

filter ที่อ่าน stream ก่อน controller ทำให้ stream ว่าง ใช้ ContentCachingRequestWrapper หากจำเป็น

Global @RequestBody binding

@ControllerAdvice ที่ parse JSON เร็วอาจเปลี่ยน byte แยก webhook ด้วย byte[]

Handler ช้ากระตุ้น retry

ตอบเร็ว ประมวลผล async dedupe ด้วย event ID

อ่านเพิ่ม

พื้นฐานอ่านที่ พื้นฐาน localhost tunneling และ การดีบัก webhook ในเครื่อง. กลไกลายเซ็นอ่านที่ คู่มือตรวจสอบลายเซ็น. handler ป้องกันซ้ำอ่านที่ รูปแบบ retry และ idempotency. เริ่มใช้ PortPreview ฟรี.

คำถามที่พบบ่อย

ทำไม Spring Boot webhook คืน 403?
CSRF ของ Spring Security บล็อก POST ที่ไม่มี session token เพิ่ม csrf.ignoringRequestMatchers สำหรับ path webhook แล้วตรวจลายเซ็น
ใช้ byte[] หรือ POJO สำหรับ webhook?
ใช้ byte[] สำหรับตรวจลายเซ็น POJO binding reserialize JSON ทำให้ HMAC พัง
ทดสอบ Spring Boot webhook ในเครื่องอย่างไร?
รัน spring-boot:run ที่ 8080 เปิดด้วย npx portpreview วาง URL tunnel และทริกเกอร์ event ทดสอบ