Все статьи
RestController Spring Boot на embedded Tomcat проверяет заголовки подписи вебхука из сырого byte[] payload через локальный туннель.
Spring BootJavawebhook debugginglocal testing

Вебхуки Spring Boot: сырой body, исключение CSRF и заголовки подписи

Spring Boot упрощает webhook-эндпоинты через @RestController, но Spring Security CSRF и JSON-конвертеры могут блокировать или менять payload до handler. Локальное тестирование должно подтвердить доступ к byte[], исключение CSRF для webhook-путей и проверку подписи по нетронутым байтам.

Создайте webhook-эндпоинт @RestController

Держите контроллеры тонкими. Принимайте сырые байты, проверяйте подпись, затем десериализуйте:

@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 — идеально когда провайдер подписывает сырой JSON.

Читать сырой body через HttpServletRequest

Альтернатива — читать напрямую из servlet-запроса:

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

Не биндите сначала к POJO или Map — пересериализация Jackson ломает HMAC. См. руководство по проверке подписи.

Отключить CSRF только для webhook-путей

Spring Security включает CSRF по умолчанию. Webhook POST падают с 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 глобально.

Проверять заголовки подписи до бизнес-логики

Читайте заголовки провайдера, вычисляйте digest из byte[] и сравнивайте за константное время. 401 при ошибке, быстрый 200 при успехе.

Локальный workflow с туннелем

  1. Запуск: ./mvnw spring-boot:run на порту 8080.
  2. Откройте Tomcat: npx portpreview 8080.
  3. Вставьте URL туннеля + путь в dashboard.
  4. Отправьте тестовое событие и смотрите логи.
  5. Повторите event ID для идемпотентности — см. паттерны повторов.

Типичные ловушки

Порядок фильтров и потребление body

Фильтры, читающие stream до контроллера, оставляют пустой поток. Используйте ContentCachingRequestWrapper.

Глобальный @RequestBody binding

Глобальный @ControllerAdvice с ранним JSON-парсингом меняет байты. Изолируйте webhook с byte[].

Медленные handler вызывают повторы

Быстро подтверждайте, обрабатывайте async, дедуплицируйте по event ID.

Дальше

Основы — в основах туннелирования localhost и практической локальной отладке вебхуков. Механика подписей — в руководстве по проверке подписи. Безопасные от повторов обработчики — в паттернах повторов и идемпотентности. начните PortPreview бесплатно.

Часто задаваемые вопросы

Почему webhook Spring Boot возвращает 403?
CSRF Spring Security блокирует POST без session token. Добавьте csrf.ignoringRequestMatchers для webhook-пути и проверяйте подпись.
byte[] или POJO для вебхуков?
Используйте byte[] для проверки подписи. POJO binding пересериализует JSON и ломает HMAC.
Как тестировать Spring Boot вебхуки локально?
Запустите spring-boot:run на 8080, откройте через npx portpreview, вставьте URL туннеля и отправьте тестовые события.