[Spring] Bean-Validation 유효성 검사

드코딩·2024년 8월 5일
0

Spring

목록 보기
5/6
post-thumbnail

유효성 검사란?

사용자가 보낸 데이터 값이 백엔드에서 취급하는 데이터와 같은 형태인지 확인하는 것이다.
이렇게 유효성 검사를 하지 않으면 비즈니스 로직에서 오류가 발생하기 때문에 사전 차단하기 위함이다.

@Valid 동작원리

모든 요청은 필터 → 디스패처 서블릿 → 핸들러 → 컨트롤러의 경로를 통해 전달되는데
전달 과정에서 컨트롤러 메소드의 객체를 만들어주는 ArgumentResolver 가 동작한다.

@Valid 또한 ArgumentResolver 에 의해 처리된다.

유효성을 검사하는 과정에서 오류가 있다면 MethodArgumentNotValidException 예외가 발생하고, 디스패처 서블릿에 기본으로 등록된 예외 리졸버인 DefaultHandlerExceptionResolver 에 의해 400 BadRequest 에러가 발생한다.

@Valid 는 기본적으로 컨트롤러에서만 동작하며 기본적으로 다른 계층에서는 검증되지 않는다.

다른 계층에서 파라미터를 검증하기 위해서는 @Validated 와 결합하여야 한다.

의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-validation'

왜 의존성을 추가해서 사용해야 하죠?

Hibernate Validator는 Jakarta Bean Validation 의 구현체이기 때문에 의존성을 추가하지 않는다면 어노테이션 추가는 가능하지만 사용하지 못할 것이다. (Jakarta Bean Validation 명세만 해둔 인터페이스)

스프링 부트 3에서 유효성 검증 기능 변화

@Constraint 애노테이션들이 기본적으로 지원되기 시작했다.

따라서 @Valid 또는 @Validated 를 붙여줄 필요없이 기본적인 유효성 검증이 동작한다.

@Validated?

입력 파라미터의 유효성 검증은 컨트롤러에서 최대한 처리하고 넘겨주는 것이 좋다. 하지만 개발을 하다 보면 항상 컨트롤러에서만 가능하지는 않을 것이다.

컨트롤러가 아닌 다른 곳에서는 어떻게 할까?

Spring에서는 AOP 기반으로 메소드의 요청을 가로채서 유효성 검증을 진행해주는 @Validated를 제공하고 있다. 아래 코드와 같이 클래스에 @Validated 를 사용하고 유효성을 검사할 파라미터에 @Valid 를 붙여준다.

@Service
@Validated
public class FooService {
		public void addFoo(@Valid AddFooRequest addFooRequest) {
			...
		}
}

유효성 검사가 실패한다면?

이전 @Valid 에서는 MethodArgumentNotValidException 예외가 발생하지만 @Validated 의 경우에는 ConstraintViolationException 예외가 발생한다.

발생하는 예외 이름이 다른 것으로 보아 동작원리가 다르겠다는 짐작이 든다.

둘 차이는 무엇일까?

@Validated 는 스프링 전용 검증 애노테이션, @Valid 는 자바 표준 검증 애노테이션이다.
둘 중 아무거나 사용해도 동일하지만, @Validated 는 내부에 groups 라는 기능을 포함하고 있다.

Bean Validation - groups

동일한 모델 객체를 등록할 때와 수정할 때 각각 다르게 검증하는 방법을 알아보자

groups를 사용하기 위해서는 인터페이스를 만들어야 한다.

각 그룹별로 인터페이스 만들기

@NotNull(groups = UpdateCheck.class)

@NotBlank(groups= { SaveCheck.class, UpdateCheck.class})

그룹화 하면 각각 맞는 상황에 검증 로직을 실행한다.

하지만 사용하기 위해서는 해당하는 메소드 @validated(value = UpdateCheck.class) 를 컨트롤러에 적용해야 한다.

정리하면서도 느꼈지만, 너무 복잡하고 코드라 지저분해지기 때문에 자주 사용하지 않는다.

  • 어떻게 해결할 수 있을까?

등록 폼을 받는 객체와 수정 폼을 받는 객체를 따로 분리해서 받는다면 이런 문제를 해결할 수 있다.

DTO를 사용해서 검증하는 객체를 분리하자!

Bean Validation - HTTP 메시지 컨버터

클라이언트 사이드의 경우 @RequestBody 로 받는 JSON 객체 옆에 @Valid 어노테이션을 붙여서 사용 가능하다. 이 경우 세 가지의 경우로 분류할 수 있다.

  • 성공 요청
  • 실패 요청 클라이언트로부터 Request를 받아서 객체로 DTO 객체로 전환하는 데 실패하면 컨트롤러 내부 코드는 실행되지 못하고 에러를 반환한다.
  • 검증 오류 요청 Http 요청으로 전달받은 데이터를 DTO 객체로 변환하는 것까지는 성공하였지만, 유효성 검사에 실패하면
    Errors , Result 등을 사용하여 에러를 처리할 수 있다.
💡 **@ModelAttribute vs @RequestBody**

HTTP 요청 파라미터를 처리하는 @ModelAttribute 는 각각의 필드 단위로 세밀하게 적용되기 때문에 특정 필드의 타입이 맞지 않더라고 나머지 필드는 정상처리할 수 있다.

하지만 HttpMessageConverter 는 각 필드 단위로 적용되는 것이 아니라, 전체 객체 단위로 적용된다. 따라서 메시지 컨버터의 작동이 성공하지 못한 경우 @valid, @Validate 가 적용된다.

@ModelAttribute 는 특정 필드가 바인딩 되지 않아도 나머지는 정상 바인딩되어 검증 적용 가능
@RequestBody 는 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다. 나머지 필드도 검증 불가능

0개의 댓글

관련 채용 정보