스프링 부트 - 유효성 검사

Spring

Posted by kwon on 2020-04-21

유효성 검사

  • 폼 데이터가 적합한지 체크하는 기능은 스프링이 제공해주는 Validator 인터페이스를 구현하거나, JSR 303 Validation을 사용하는 방법이 있다.
  • 백기선님의 스프링 강좌 내용을 가져와 예시로 적용하였다.

회원가입 폼 검증 - JSR 303 어노테이션 검증

  • 값의 길이나 필수값 등을 검증한다.

  • AccountController.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    @Controller
    @RequiredArgsConstructor
    public class AccountController {

    private final SignUpFormValidator signUpFormValidator;

    @InitBinder("signUpForm") // signUpForm 이라는 데이터를 받을 때 바인더를 설정
    public void initBinder(WebDataBinder webDataBinder){
    // Validator를 추가
    // SignUpForm 의 타입과 매핑이되어 Validator가 사용됨.
    webDataBinder.addValidators(signUpFormValidator);
    }

    @GetMapping("/sign-up")
    public String signUpForm(Model model){
    model.addAttribute("signUpForm", new SignUpForm());
    return "account/sign-up";
    }

    @PostMapping("/sign-up") // 파라미터에서는 @ModelAttribute 생략 가능
    public String signUpSubmit(@Valid @ModelAttribute SignUpForm signUpForm, Errors errors) {
    // @Valid : jsr 303 어노테이션들의 조건을 만족하는지 확인
    if (errors.hasErrors()) {
    return "account/sign-up";
    }
    // @InitBinder로 대체
    /* signUpFormValidator.validate(signUpForm, errors);
    if (errors.hasErrors()) {
    return "account/sign-up";
    }*/

    // TODO 회원 가입 처리
    return "redirect:/";
    }

    }
  • @ModelAttribute

  • 매개변수로 선언하는 경우

    • 파라미터로 넘겨 준 타입의 오브젝트를 자동으로 생성 (이때, @ModelAttribute가 지정되는 클래스는 getter와 setter가 명명 규칙에 맞게 만들어져 있어야 한다.)
    • 생성된 오브젝트에 HTTP로 넘어온 값들을 자동으로 바인딩한다. 위의 코드에서는 SignUpForm에 있는 nickname, email, password 속성 값들이 해당 변수의 setter를 통해 해당 멤버 변수에게로 binding 된다.
    • @ModelAttribute어노테이션이 붙은 객체가 자동으로 Model객체에 추가된다.
    • 위와 같이 파라미터에 붙이는 경우에는 생략이 가능하다.
  • 메소드에 선언하는 경우

    • View에서 사용할 데이터를 설정하는 용도로 사용
    • @ModelAttribute가 설정된 메소드는 @RequestMapping어노테이션이 적용된 메소드보다 먼저 호출
    • @ModelAttribute 메소드 실행 결과로 리턴되는 객체는 자동으로 Model에 저장
    • @ModelAttribute 메소드 실행 결과로 리턴된 객체를 View 페이지에서 사용 가능
  • @Valid : 요청 데이터를 검증하는 어노테이션

  • @Valid를 이용한 자동 검증

    • 컨트롤러 메소드의 @ModelAttribute 파라미터에 @Valid 애노테이션을 추가한다. 그러면 validate() 메소드를 실행하는 대신 바인딩 과정에서 자동으로 검증이 진행된다.
    • Validation 과정에서 실패하거나 에러가 발생하면 Errors에 에러들이 담기게 된다.
      • errors.hasErrors()를 이용하여 에러 발생 시 예외처리를 할 수 있다.

  • SignUpForm.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Data
    public class SignUpForm {

    @NotBlank // 비어있는 값이면 안됨.
    @Length(min = 3, max = 20) // 문자열의 길이 지정 3 ~ 20
    @Pattern(regexp = "^[ㄱ-ㅎ가-힣a-z0-9_-]{3,20}$") // 정규표현식으로 사용 가능한 패턴 지정
    private String nickname;

    @Email // email 형식의 문자열
    @NotBlank
    private String email;

    @NotBlank
    @Length(min = 8, max = 50)
    private String password;
    }
    • @Data : 롬복이 제공하는 어노테이션, @getter, @setter, @RequiredArgsConstructor, @equalsAndHashcord, @ToString을 한번에 설정해 주는 간축형 어노테이션
  • 유효성 검사 어노테이션
    • @NotBlank : 문자열이나 배열의 경우 null이 아니고 길이가 0이 아닌지 검사
    • @NotNull : 변수 값이 null인지 아닌지 검사
    • @Pattern(regexp = ) : 변수 값이 정규표현식을 만족하는지 검사
    • @Email : 이메일 형식을 만족하는지 검사
    • @Size(min=, max=) : 문자열, 배열 등의 크기가 지정된 크기를 만족하는지 검사
    • @Length(min=, max=) : 문자열의 길이가 지정된 크기를 만족하는지 검사(Hibernate 제공)
    • @Past : 해당 시간이 과거의 시간인지 검사
    • @Future : 해당 시간이 미래의 시간인지 검사
    • @AssertTrue : 변수 값이 true인지 검사
    • @AssertFalse : 변수 값이 flase인지 검사

커스텀 검증 - Validator 인터페이스 구현

  • Spring은 도메인 객체를 검증할 수 있도록 Validator 인터페이스를 도입했다. Validator 인터페이스는 객체를 검증하는데 실패하면 Errors 객체에 에러를 등록함으로써 동작한다.

  • 이메일, 닉네임 중복 확인을 위해 Validator인터페이스를 구현한다.

  • SignUpFormValidator.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import lombok.RequiredArgsConstructor;
    import org.springframework.stereotype.Component;
    import org.springframework.validation.Errors;
    import org.springframework.validation.Validator;

    @Component
    @RequiredArgsConstructor // lombok이 제공하는 어노테이션, private final 타입의 맴버 변수의 생성자를 만들어준다.
    public class SignUpFormValidator implements Validator {

    private final AccountRepository accountRepository;

    @Override
    public boolean supports(Class<?> clazz) {
    // SignUpForm 타입의 인스턴스를 검사
    return clazz.isAssignableFrom(SignUpForm.class);
    }

    @Override
    public void validate(Object target, Errors errors) {
    // 이메일, 닉네임 중복 검사
    SignUpForm signUpForm = (SignUpForm)target;
    if (accountRepository.existsByEmail(signUpForm.getEmail())){
    errors.rejectValue("email", "invalid.email", new Object[]{signUpForm.getEmail()}, "이미 사용중인 이메일입니다.");
    }

    if (accountRepository.existsByNickname(signUpForm.getNickname())){
    errors.rejectValue("nickname", "invalid.nickname", new Object[]{signUpForm.getNickname()}, "이미 사용중인 닉네임입니다.");
    }
    }
    }
  • Validator 인터페이스는 두 가지 메서드를 가지고 있다.

    • supports(Class) : 매개변수로 전달된 클래스를 검증할 수 있는지 여부를 반환
    • validate(Object, Errors) : 매개변수로 전달된 객체를 검증하고 실패하면 Errors객체에 에러를 등록
      • org.springframework.validation.Errors임을 주의
  • @RequiredArgsConstructor : lombok이 제공하는 어노테이션, private final 타입의 맴버 변수의 생성자를 만들어준다.

    • 다음과 같은 의미
      1
      2
      3
      public SignUpFormValidator(AccountRepository accountRepository) {
      this.accountRepository = accountRepository;
      }
  • (스프링 4.2 이후 어떤 빈이 생성자가 하나이고 그 생성자가 받는 파라미터들이 빈으로 등록이 되어있다면 자동으로 빈을 주입해주기 때문에 @Autowired @Inject 없이도 의존성 주입이 됨.)

  • AccountRepository.java
    1
    2
    3
    4
    5
    6
    @Transactional(readOnly = true)
    public interface AccountRepository extends JpaRepository<Account, Long> {
    boolean existsByEmail(String email);

    boolean existsByNickname(String nickname);
    }
  • @Transactional(readOnly = true) : 트랜잭션을 읽기 전용으로 설정 (해당 nickname, mail의 존재 여부만 확인)

참조
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1/dashboard
https://webcoding.tistory.com/entry/Spring-JSR-303-%EC%9C%BC%EB%A1%9C-%EA%B0%9D%EC%B2%B4-%EA%B0%92-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0
https://velog.io/@junwoo4690/Spring-boot-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8%EC%97%90%EC%84%9C-request-Validation-%EC%9A%94%EC%B2%AD%EA%B0%92-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0
https://velog.io/@junwoo4690/Spring-boot-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8%EC%97%90%EC%84%9C-request-Validation-%EC%9A%94%EC%B2%AD%EA%B0%92-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0
https://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte2:ptl:jsr303