클라이언트로부터 전송되는 API Payload에 대해서 검증이 이뤄지지 않아 비즈니스 로직에서 에러가 발생하며 SQL Injection이나 Command Injection과 같은 공격에 대한 취약점이 존재할 수 있을 것이라 생각했다. 요청 API 처리의 시작부분인 Controller에서 Payload를 검증하여 보안성과 안정성을 향상시키고자 한다.
@Valid / @Validated
Spring Boot에서 유효성을 검증하는 어노테이션은 두 가지가 존재한다. 각 어노테이션의 차이점을 알아보고 코드에 적용해보도록 하겠다.
1. @Valid
@Valid는 JSR-303 표준 스펙으로 Bean Validator를 이용해 객체의 제약 조건을 검증하는 어노테이션
Spring에서는 LocalValidatorFactoryBean이 제약 조건 검증을 처리
@Valid는 주로 Controller에서 Request Body를 검증하는데 사용
1.1 @Valid의 동작 원리
Spring Boot의 모든 요청은 Servlet을 통해 Controller로 전달
전달 과정에서 Controller Method의 객체를 만들어주는 ArgumentResolver가 동작하는데 @Valid도 해당 Resolver에 의해 처리
대표적으로 @RequestBody는 JSON 메세지를 객체로 변환하는 작업을 ArgumentResolver의 구현체인 RequestResponseBodyMethodProccessor가 처리하며 @Valid로 시작하는 모든 경우에 검사를 진행한다. 이러한 이유에서 @Valid는 기본적으로 Controller에서만 작동을 하게 된다.
만약, 검증에 오류가 있다면 MethodArgumentNotValidException 예외가 발생하며 DefaultHandlerExceptionResolver에 의해 400 Bad Request를 발생시킨다.
2. @Validated
@Validated는 Spring AOP를 기반으로 메소드의 요청을 가로채서 유효성을 검증하는 어노테이션
Spring에서는 MethodValidationInterceptor가 제약 조건 검증을 처리
@Validated는 주로 @PathVariable, @RequestParam, 컨트롤러 외의 다른 계층에서 유효성을 검증하는데 사용
1.1 @Validated의 동작 원리
@Validated를 클래스 레벨에 선언하면 유효성 검증을 위한 AOP Advice 또는 인터셉터(MethodValidationInterceptor)가 등록
클래스에 있는 Method들이 호출될 때 AOP의 Pointcut으로 요청을 가로채서 유효성 검증
이러한 이유로 @Validated를 사용하면 Controller, Service, Repository 등 계층과 무관하게 Spring Bean이라면 유효성을 검증할 수 있다. 이를 적용하기 위해서는 클래스에 @Validated를 작성하고 메소드에는 Validated 어노테이션을 선언하면 된다.
만약, 검증에 오류가 있다면 ConstraintViolationException 예외가 발생하며 DefaultHandlerExceptionResolver에 선언되어 있지 않아 500 에러를 발생시킨다.
구현
AutoConfiguration
Spring Boot AutoConfigure를 보면 ValidationAutoConfiguration이 등록되어 있는 것을 확인할 수 있다. 해당 설정은 build.gradle에 spring-boot-starter-validation을 추가하면 자동으로 설정한다.
@Valid를 처리하는 LocalValidatorFactoryBean과 MethodValidationInterceptor 동작을 위해 MethodValidationPostProcessor를 빈으로 등록하며 @Validated 처리를 위해 설정하는 것을 알 수 있다.