본문 바로가기
Dev/Spring Boot

Spring Boot + JPA 회원가입 유효성 검사

by yeonise 2022. 10. 7.

회원가입 시 사용자가 입력한 정보가 서버로 전송되기 전에 특정 규칙에 맞게 입력했는지, 이미 사용하고 있는 닉네임이나 이메일은 아닌지 확인하는 검증 단계가 필요하다.

 

참고

- @Validated : 스프링 전용 검증 애너테이션

- @Valid : 자바 표준 검증 애너테이션

 

클라이언트 검증과 서버 검증을 적절히 섞어서 사용하되, 최종적으로 서버 검증은 필수이다.

검증 로직을 모든 프로젝트에 적용할 수 있게 표준화한 것이 바로 Bean Validation이다.

 

✓ Validation 유효성 검사하기

1. build.gradle 설정하기

validation 의존성을 추가한다.

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

 

 

2. MemberDto

유효성 검사가 필요한 Dto 객체에 Validation 애너테이션을 사용한다.

public class MemberDto {

    @NotBlank(message = "이름은 필수 입력 값입니다.")
    private String name;

    @NotBlank(message = "닉네임은 필수 입력 값입니다.")
    @Pattern(regexp = "^[가-힣a-zA-Z0-9]{2,10}$" , message = "특수문자를 포함하지 않은 2자 이상 10자 이하 단어로 입력해주세요.")
    private String nickname;

    @NotEmpty(message = "이메일은 필수 입력 값입니다.")
    @Email(message = "이메일 형식으로 입력해주세요.")
    private String email;
    
}

 

 

3. MemberController

BindingResult는 error를 자동으로 model에 담아 전달한다.

@PostMapping(value = "/register/member")
    public String registerMember(@Valid MemberDto memberDto, BindingResult bindingResult, Model model) {
        if (bindingResult.hasErrors()) {
            return "member/member-register";
        }

        try {
            Member member = Member.createMember(memberDto, passwordEncoder);
            memberService.saveMember(member);
        } catch (IllegalStateException e) {
            model.addAttribute("errorMessage", e.getMessage());
            return "member/member-register";
        }

        return "member/register-complete";
    }

 

 

✓ 중복 확인하기

1. MemberRepository

boolean true : 중복 O

boolean false : 중복 X

 

JPA는 해당 데이터가 DB에 존재하는지 확인할 때 existsBy 키워드를 사용한다. 해당 데이터가 존재하는 경우 true, 존재하지 않는 경우 false를 return 한다.

boolean existsByEmail(String email);
boolean existsByNickname(String nickname);

 

 

2. MemberService

public boolean checkEmailDuplicate(String email) {
    return memberRepository.existsByEmail(email);
}

public boolean checkNicknameDuplicate(String nickname) {
    return memberRepository.existsByNickname(nickname);
}

 

 

3. MemberController

@GetMapping("/email/{email}/exists")
public ResponseEntity<Boolean> checkEmailDuplicate(@PathVariable String email) {
    return ResponseEntity.ok(memberService.checkEmailDuplicate(email));
}

@GetMapping("/nickname/{nickname}/exists")
public ResponseEntity<Boolean> checkNicknameDuplicate(@PathVariable String nickname) {
    return ResponseEntity.ok(memberService.checkNicknameDuplicate(nickname));
}

 

 

4. Validator 구현체 AbstractValidator 생성하기

Validator 클래스를 구현한 AbstractValidator 추상 클래스를 생성한다.

@Slf4j
public abstract class AbstractValidator<T> implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void validate(Object target, Errors errors) {
        try {
            doValidate((T) target, errors); // 유효성 검증 로직
        } catch (IllegalStateException e) {
            log.error("중복 검증 에러", e);
            throw e;
        }
    }

    // 유효성 검증 로직
    protected abstract void doValidate(final T dto, final Errors errors);
}

 

- supports() :

@Validated는 검증기를 실행하라는 애너테이션이다. 이 애너테이션이 붙으면 WebDataBinder에 등록한 검증기를 찾아서 실행한다. 여러 검증기(Validator)를 등록한다면 그중 어떤 검증기로 실행되어야 할지 구분이 필요한데, 이때 supports()가 사용된다.

- doValidate() :

검증 로직이 들어갈 부분을 따로 작성하기 위해서 추가한다.

- @SuppressWarnings("unchecked") :

컴파일러에서 경고하지 않도록 하는 설정이다.

 

 

5. CheckNicknameValidator, CheckEmailValidator 클래스 작성하기

AbstractValidator 추상 클래스를 상속받은 필드 중복 검사 클래스를 작성한다. doValidate()를 오버라이딩하여 로직을 구현한다.

@RequiredArgsConstructor
@Component
public class CheckNicknameValidator extends AbstractValidator<MemberDto> {

    private final MemberRepository memberRepository;
    @Override
    protected void doValidate(MemberDto dto, Errors errors) {
        Member member = new Member();
        member.setNickname(dto.getNickname());
        if (memberRepository.existsByNickname(member.getNickname())) {
            // 중복인 경우
            errors.rejectValue("nickname", "닉네임 중복 오류", "이미 사용 중인 닉네임입니다.");
        }
    }
}

 

- rejectValue() :

깔끔하게 검증 오류를 다룰 수 있다.

void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);

 

 

6. WebDataBinder

Validator 클래스 사용을 위해 MemberController에 @InitBinder가 붙은 WebDataBinder을 인자로 받는 메서드를 작성하여 검증 Validator를 추가한다.

private final CheckNicknameValidator checkNicknameValidator;
private final CheckEmailValidator checkEmailValidator;

@InitBinder
public void validatorBinder(WebDataBinder binder) {
    binder.addValidators(checkNicknameValidator);
    binder.addValidators(checkEmailValidator);
}

 

- @InitBinder : 특정 컨트롤러에서 바인딩 또는 검증 설정을 변경하고 싶을 때 사용한다.

- WebDataBinder binder : HTTP 요청 정보를 컨트롤러 메서드의 파라미터나 모델에 바인딩할 때 사용되는 바인딩 객체이다. 스프링의 파라미터 바인딩의 역할을 해주며 검증 기능도 내부에 포함한다.

- addValidators() : addValidators() 메서드를 이용해 검증기(Validator)를 추가하면 해당 컨트롤러에서는 검증기를 자동으로 적용한다. @InitBinder에 해당하는 컨트롤러에만 영향을 준다. 즉, 컨트롤러에 요청이 올 때마다 WebDataBinder가 호출되면서 WebDataBinder에 등록한 검증기를 매번 적용하게 할 수 있다.

 

아래와 같이 View에서 에러 메시지를 사용자에게 알려줄 수 있다.

유효성 검사 결과

 

중복 확인 결과

 

 

reference

jylee의 velog

 

 

댓글