회원가입시에 아이디, 비밀번호, 이메일의 형식을 따로 정해두고 해당 형식에 맞지 않으면 회원가입을 할 수 없도록 유효성 검사를 구현해주었습니다. 인터넷에 나와 있는 자료를 바탕으로 여러가지 방법을 시도해보았으나 잘 작동하지 않는 경우가 대부분이었는데, 이틀정도 구르다보니 어찌저찌 그럴듯 하게 동작하도록 구현은 된 것 같습니다.
유효성 검사를 위한 구현 과정은 다음과 같습니다.
📝 1. UserRequestDto
- 우선 기존에 ajax에서 엔티티를 통해 데이터를 받아오던 방식에서 따로 DTO를 만들어 데이터를 받아오는 방식으로 수정하였습니다.
- 이유는 여러가지가 있는데, 가장 큰 이유는 애초에 엔티티로 유효성 검사를 수행하려 할 경우 오류가 발생하였습니다. 오류가 발생한 정확한 이유는 모르겠으나 위처럼 수정하니 오류가 해결되었습니다.
- DTO 구성은 아래와 같습니다.
package com.cos.blog.dto;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserRequestDto {
@NotBlank(message = "아이디는 필수 입력 값입니다.")
private String username;
@NotBlank(message = "비밀번호는 필수 입력 값입니다.")
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", message = "비밀번호는 8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.")
private String password;
@NotBlank(message = "이메일은 필수 입력 값입니다.")
@Pattern(regexp = "^(?:\\w+\\.?)*\\w+@(?:\\w+\\.)+\\w+$", message = "이메일 형식이 올바르지 않습니다.")
private String email;
}
📝 2. Controller 수정
- 이에따라 Controller의 회원가입 부분을 아래와 같이 수정해주었습니다.
@PostMapping("/auth/joinProc")
public ResponseDto<?> join(@Valid @RequestBody UserRequestDto userDto, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
Map<String, String> validatorResult = userService.validateHandling(bindingResult);
return new ResponseDto<>(HttpStatus.BAD_REQUEST.value(), validatorResult);
}
userService.join(userDto);
return new ResponseDto<Integer>(HttpStatus.OK.value(), 1);
}
- BindingResult는 스프링이 제공하는 객체로, 검증 오류가 발생할 시 해당 내용을 저장하는 객체입니다.
- 주의할 점으로, BindingResult 객체는 @Valid 어노테이션이 붙은 변수 뒤에, 즉 유효성 검사를 수행하고자 하는 객체의 바로 뒤에 선언해주어야 합니다.
- 이렇게 BindingResult를 사용하면 400 오류가 발생하지 않고 오류 내용을 따로 담아 컨트롤러를 정상 호출할 수 있기 때문에 오류 페이지로 넘어가기 전에 이에 대한 처리를 수행할 수 있습니다.
if(bindingResult.hasErrors()) {
Map<String, String> validatorResult = userService.validateHandling(bindingResult);
return new ResponseDto<>(HttpStatus.BAD_REQUEST.value(), validatorResult);
}
- hasErrors() 메서드는 유효성 검사를 통해 발생한 오류가 있는지를(유효성 검사에 실패했는지를) 체크하는 메서드입니다.
- validateHandling() 메서드는 Controller에서 발생한 에러를 전달받아 알맞게 정재한 뒤에 다시 반환해주는 함수로, 이렇게 정재된 반환값은 이후 View에서 사용할 것입니다.
- validateHandling() 메서드는 Service단에 아래와 같이 구현되어 있습니다.
@Transactional(readOnly = true)
public Map<String, String> validateHandling(BindingResult bindingResult) {
Map<String, String> validatorResult = new HashMap<>();
for(FieldError error : bindingResult.getFieldErrors()) {
String validKeyName = String.format("valid_%s", error.getField());
validatorResult.put(validKeyName, error.getDefaultMessage());
}
return validatorResult;
}
- 위 메서드는 유효성 검사에 실패한 필드들을 Map 자료구조를 통해 {키 값, 에러 메시지}의 형태로 응답합니다.
- Key : valid_{dto 필드명}
- Message : dto에서 작성한 message 값
📝 3. Service 수정
- 다음으로 Service 클래스는 아래와 같이 수정해주었습니다.
@Transactional
public void join(UserRequestDto userDto) {
User user = User.builder()
.username(userDto.getUsername())
.password(encoder.encode(userDto.getPassword()))
.email(userDto.getEmail())
.role(RoleType.USER)
.build();
userRepository.save(user);
}
@Transactional
public void join(User user) {
userRepository.save(user);
}
- 굳이 똑같은 이름의 메서드를 두 개 구현한 이유는 join 함수의 파라미터가 UserRequestDto로 변경되면서 이전에 구현한 카카오 로그인 기능도 수정해주어야 했기 때문입니다.
- 위의 join 함수는 일반 회원가입시에 호출될 함수이고 아래의 join 함수는 카카오 로그인시에 호출될 함수입니다.
📝 4. JS 수정
- 이제 Controller에서 반환한 Data를 js 파일에서 사용해야 하는데, 이는 아래와 같이 구현하였습니다.
$.ajax({
type: "POST",
url: "/auth/joinProc",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
//dataType: "json"
}).done(function(resp) {
if(resp.status == 400) {
alert("회원가입 입력 정보를 다시 확인해주십시오.")
if(resp.data.hasOwnProperty('valid_username')){
$('#valid_username').text(resp.data.valid_username);
$('#valid_username').css('color', 'red');
}
else $('#valid_username').text('');
if(resp.data.hasOwnProperty('valid_password')){
$('#valid_password').text(resp.data.valid_password);
$('#valid_password').css('color', 'red');
}
else $('#valid_password').text('');
if(resp.data.hasOwnProperty('valid_email')){
$('#valid_email').text(resp.data.valid_email);
$('#valid_email').css('color', 'red');
}
else $('#valid_email').text('');
}
else {
alert("회원가입이 완료되었습니다.");
location.href = "/";
}
}).fail(function(error) {
alert(JSON.stringify(error));
});
- 즉, 유효성 검사 실패 유형에 따라 알맞은 에러 메시지를 사용자에게 출력하는 것입니다.
📝 5. View 수정
- 이제 View를 아래와 같이 수정해주면 끝납니다.
- id 속성이 'valid_%'의 형태로 되어있는 <p> 태그를 추가하였으며, 해당 위치에 오류 메시지가 출력될 것입니다.
<div class="container">
<form>
<div class="form-group">
<label for="username">Username</label> <input type="text" class="form-control" placeholder="Enter username" id="username">
</div>
<p id="valid_username"></p>
<div class="form-group">
<label for="password">Password</label> <input type="password" class="form-control" placeholder="Enter password" id="password">
</div>
<p id="valid_password"></p>
<div class="form-group">
<label for="email">Email address</label> <input type="email" class="form-control" placeholder="Enter email" id="email">
</div>
<p id="valid_email"></p>
</form>
<button id="btn-join" class="btn btn-primary">Join</button>
</div>
📝 6. 결과 확인
💡 알게 된 점
- @Valid 어노테이션과 BindingResult 객체를 사용하여 유효성 검사를 수행하는 방법
- 유효성 검사 이후 실패 조건에 따라 서로 다른 오류 메시지를 클라이언트 측에 전달하는 방법
'🚗 Backend Toy Project > 스프링 부트 게시판' 카테고리의 다른 글
[스프링부트 게시판] 27. 게시글 작성일 및 조회수 추가 (0) | 2022.07.03 |
---|---|
[스프링부트 게시판] 26. 커스텀 Validation을 통한 중복 검사 구현 (0) | 2022.07.02 |
[스프링부트 게시판] 24. Remember Me 기능 구현 (0) | 2022.06.29 |
[스프링부트 게시판] 23. 로그인 실패 예외 처리 (0) | 2022.06.28 |
[스프링부트 게시판] 22. 개선 및 수정사항 (0) | 2022.06.25 |