Spring Validation으로 잘못된 요청 데이터 차단하기

최정윤·2026년 7월 8일

Spring

목록 보기
14/39

잘못된요청도 서버에 들어올 수 있다

회원가입 API를 만든다고 가정하면 사용자는 이름, 이메일, 비밀번호, 나이, 전화번호를 입력한다.
문제는 사용자가 항상 올바른 값을 보내지는 않는다는 점이다.

예를 들어 이메일 칸에 다래와 같은 값을 보낼 수 있다.

{
  "email": "asd.com"
}

asd.com은 이메일처럼 보이지만 실제 이메일 형식은 아니다.
일반적으로 이메일은 @가 포함되어야 한다.

검증이 없다면 서버는 이 값을 정상 데이터로 받아들일 수 없다. 실제 실습에서도 이메일 형식이 아닌 asd.com을 보냈는데 200OK와 함께 "가입이 완료되었습니다."라는 응답이 반환되었다.

검증 전: 잘못된 이메일이 200 OK로 통과함
Postman에서 email값으로 asd.com을 보냈는데 서버가 200OK를 반환한 화면이다. 이 이미지는 Validation이 없으면 잘못된 요청도 정상 처리될 수 있음을 보여준다.


Validation은 Controller 입구에서 요청을 검사하는 장치다

Validation은 클라이언트가 보낸 요청 데이터가 서버에서 정한 규칙에 맞는지 확인하는 과정이다.
Controller는 HTTP 요청을 가장 먼저 받는 계층이기 때문에, 잘못된 요청을 Service나 DB까지 보내기 전에 검사하는 역할을 한다.

비유하면 Controller는 음식점의 주문 접수대와 비슷하다.

손님 요청: "메뉴판에 없는 음식을 주세요"
        ↓
접수대에서 확인
        ↓
잘못된 주문이면 주방까지 보내지 않음

서버 요청도 마찬가지이다.

Postman JSON 요청
        ↓
Controller
        ↓
Validation 검사
        ↓
정상 요청이면 Service로 이동
잘못된 요청이면 400 Bad Request 반환

잘못된 요청을 미리 막지 않으면 의미 없는 데이터가 DB에 저장되거나, 서버 내부 로직에서 오류가 발생할 수 있다.


검증은 한 곳에서만 하면 부족하다

검증은 보통 세 단계로 나눠서 생각할 수 있다.

구분역할특징
프론트엔드 검증사용자가 입력할 때 바로 안내사용성은 좋지만 사용자가 우회할 수 있음
서버 검증API 요청이 유효한지 확인백엔드에서 반드시 필요
DB 검증NOT NULL, 기본값, 제약조건 등최종 방어선 역할

프론트엔드 검증은 사용자에게 빠르게 알려줄 수 있어서 필요하다.
하지만 브라우저 조작이나 Postman 요청처럼 우회가 가능하므로 보안상 최종 검증이 될 수 없다.

따라서 서버 검증은 선택이 아니라 필수다. DB 제약조건은 마지막 방어선이지만, DB까지 잘못된 요청이 도달하기 전에 Controller에서 막는 것이 더 안전하다.


Bean Validation은 if문 검증을 @(어노테이션)으로 바꿔준다

Bean Validation을 사용하지 않으면 검증 코드를 직접 작성해야 한다.

if (email == null || email.isEmpty()) {
    return "이메일을 입력하세요";
}

if (!email.contains("@")) {
    return "올바른 이메일이 아닙니다";
}

if (password == null || password.length() < 8) {
    return "비밀번호는 8자 이상이어야 합니다";
}

이 방식의 코드는 이해하기 쉽지만, 필드가 많아질수록 문제가 생긴다.

방식장점문제점
if문 검증직접 흐름을 볼 수 있음코드가 길어지고 반복이 많아짐
어노테이션 검증규칙이 필드 옆에 붙어서 읽기 쉬움@Valid 실행 흐름을 이해해야 함

Bean Validation을 사용하면 검증 규칙을 DTO 필드에 선언할 수 있다.

@Getter
public class SaveMemberRequestDto {

    @NotBlank
    private String name;

    @Email
    private String email;

    @Size(min = 8, max = 20)
    private String password;

    @Min(19)
    private Integer age;

    @Pattern(regexp = "^010-\\d{4}-\\d{4}$")
    private String phone;
}

각 어노테이션(@)의 역할은 다음과 같다.

어노테이션역할
@NotBlanknull, 빈 문자열, 공백 문자열 방지
@Email이메일 형식인지 확인
@Size문자열 길이 또는 컬렉션 크기 제한
@Min / @Max숫자의 최소값, 최대값 제한
@Pattern정규식을 이용한 문자열 형식 제한

검증 규칙이 DTO 필드 바로 위에 있으므로, 어떤 값에 어떤 조건이 필요한지 한눈에 볼 수 있다.


Validation이 동작하기 위한 조건

Validation이 자동으로 동작하려면 핵심 조건이 필요하다.

조건설명
validation 의존성spring-boot-starter-validation이 필요함
DTO 검증 어노테이션@Email, @NotBlank, @Size 같은 규칙 작성
Controller의 @ValidDTO에 적힌 검증 규칙을 실행
JSON → DTO 변환 가능요청 본문이 DTO 객체로 변환될 수 있어야 함

핵심은 두 가지다.

// DTO: 검증 규칙 작성
@Email
private String email;
// Controller: 검증 실행
@PostMapping("/signup")
public String signup(@Valid @RequestBody SaveMemberRequestDto request) {
    return "가입이 완료되었습니다.";
}

@Email 같은 어노테이션은 “검사 기준표”이고, @Valid“검사 시작 버튼”이다.
검사 기준표만 붙여놓고 검사 시작 버튼을 누르지 않으면 검증이 실행되지 않는다.

DTO 검증 규칙과 Controller의 @Valid
왼쪽에는 SaveMemberRequestDto@Email, @Size, @Min, @Pattern이 있고, 오른쪽에는 Controller@Valid @RequestBody가 있는 화면이다. DTO에는 검증 규칙을 작성하고, Controller에서는 그 규칙을 실행한다는 흐름을 보여준다.


@Getter는 Validation의 필수 조건이 아니다

실습 코드에는 DTO에 @Getter가 붙어 있다.
하지만 @Getter는 Validation을 실행하는 필수 조건이 아니다.

요소역할
@Email, @NotBlank검증 규칙
@Valid검증 실행
@Getterprivate 필드 값을 읽을 수 있는 getter 생성

즉, @Getter는 DTO 값을 읽기 위한 Lombok 어노테이션이고, Validation 자체의 핵심 조건은 아니다.

정확히 정리하면 다음과 같다.

Validation의 핵심 조건
= validation 의존성 + DTO 검증 어노테이션 + Controller의 @Valid

실습 결과: 잘못된 이메일 요청이 차단되었다

처음에는 이메일 값이 asd.com이어도 서버가 200 OK를 반환했다.

이후 DTO에 검증 어노테이션을 붙이고, Controller에서 @Valid를 적용한 뒤 같은 요청을 다시 보냈다.

{
  "name": "안녕하세요",
  "email": "asd.com",
  "password": "12345678",
  "age": 23,
  "phone": "010-1111-2222"
}

이번에는 응답이 달라졌다.

{
  "timestamp": "...",
  "status": 400,
  "error": "Bad Request",
  "path": "/signup"
}

결과적으로 잘못된 이메일 형식이 Controller 단계에서 차단되었다.
검증 실패 시 자동으로 400 Bad Request가 반환된다는 점을 확인할 수 있었다.

검증 후: 잘못된 이메일이 400 Bad Request로 차단됌
같은 요청에서 email 값은 여전히 asd.com이지만, Validation 적용 후에는 서버가 400 Bad Request를 반환한 화면이다.


정규식은 암기 대상이 아니라 형식 검증 도구다

@Pattern은 정규식을 사용해 문자열 형식을 검사할 때 사용한다.

예를 들어 전화번호 형식을 검사할 때 아래처럼 사용할 수 있다.

@Pattern(regexp = "^010-\\d{4}-\\d{4}$")
private String phone;

이 코드는 010-1234-5678 같은ㅇ 형식을 기대한다.

다만 정규식 패턴을 외우는 것이 목적은 아니다.
정규식은 "이런 형식이어야 한다"를 표현하는 도구이고, 필요한 패턴은 검색해서 의미를 확인한 뒤 적용하면 된다.

지금 단계에서는 아래 정도만 이해하면 충분하다.

정규식 = 문자열 형식을 검사하는 규칙
@Pattern = 정규식을 Validation에 적용하는 어노테이션
패턴 암기 = 하지 않음
필요할 때 = 검색해서 사용

실습 중 알게 된 점

잘못된 요청이 200 OK로 통과되는 것이 더 위험하다

처음에는 400 Bad Request가 에러처럼 보여서 나쁜 결과처럼 느껴질 수 있다.
하지만 서버 관점에서는 잘못된 요청을 정상 처리하는 200 OK가 더 위험하다.

상황의미
잘못된 요청인데 200 OK서버가 잘못된 데이터를 정상으로 받아들임
잘못된 요청이라 400 Bad Request서버가 요청 문제를 정확히 감지하고 차단함

따라서 Validation 실습의 성공 결과는 400 Bad Request다.


전체 흐름 정리

1. 사용자가 JSON 요청을 보냄
        ↓
2. @RequestBody가 JSON을 DTO 객체로 변환
        ↓
3. @Valid가 DTO의 검증 어노테이션을 실행
        ↓
4. DTO 필드 값이 규칙에 맞는지 검사
        ↓
5-1. 성공하면 Controller 메서드 실행
5-2. 실패하면 400 Bad Request 반환

핵심은 "잘못된 요청을 어디에서 막을 것인가"이다.
Service나 DB까지 보낸 뒤 막는 것보다, Controller 입구에서 먼저 막는 것이 더 안전하고 명확하다.


마무리

핵심 질문정리
Validation은 왜 필요한가?잘못된 요청 데이터가 서버 내부로 들어오는 것을 막기 위해 필요하다.
어디에서 주로 실행되는가?HTTP 요청을 받는 Controller 계층에서 실행된다.
DTO는 Spring Bean인가?아니다. 요청마다 만들어지는 데이터 전달 객체다.
그런데 왜 Bean Validation인가?여기서 Bean은 Spring Bean이 아니라 검증 대상 Java 객체를 의미한다.
@Getter는 필수인가?아니다. Validation의 필수 조건은 아니다.
@Valid는 어떤 역할인가?DTO에 작성된 검증 규칙을 실행한다.
정규식은 외워야 하는가?아니다. 필요한 패턴을 검색해서 적용하면 된다.

Spring Validation은 단순히 에러를 발생시키는 기능이 아니라, 서버가 받아들일 수 있는 요청과 거절해야 하는 요청을 구분하는 장치다.
이번 실습에서는 이메일 형식이 아닌 값이 처음에는 200 OK로 통과되었고, Validation 적용 후에는 400 Bad Request로 차단되었다.

이를 통해 Validation의 목적은 “코드를 예쁘게 만드는 것”이 아니라, 잘못된 요청이 비즈니스 로직과 DB까지 흘러가지 않도록 서버의 입구에서 방어하는 것임을 확인했다.

profile
콩떡

0개의 댓글