Validation : 유효성 검증
웹 개발을 하다보면 서버로 들어온 요청이 서버에서 필요한 데이터의 스펙에 잘 맞게 들어왔는지 검사해야할 필요가 있다.
예를들면 '나이'를 입력받아서 서버에 저장해야하는데, Integer타입의 age 변수에 숫자가 아닌 값이 들어오는 경우 오류가 발생한다. 또는 그저 사용자가 오타를 쳐서 잘못된 정보를 기입하는 경우도 생각해 볼 수 있다.
만약 사용자가 오타를 하나 쳤다고 error 페이지가 뜨고, 뒤로 돌아가 처음부터 다시 입력을 해야한다면, 그 사용자는 이 서비스를 이용하고 싶지 않을 것이다. 따라서 웹 서비스는 오류 발생시 친절하게 어디서 어떤 오류가 발생했는지 친절하게 알려주는것이 좋다.
컨트롤러의 중요한 역할중 하나는 HTTP 요청이 정상인지 검증하는 것이다.
클라이언트 검증은 조작하기 쉽기 때문에 보안에 취약하다. 서버만으로 검증하면, 즉각적인 고객의 사용성이 부족해진다.
따라서 둘을 적절히 섞어서 사용하되, 최종적으로 서버에서 필수적으로 검증해야한다.
[ 사용 세팅 ]
Bean Validation을 사용하려면 build.gradle
에 아래와 같은 의존관계를 추가해야 한다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
errors.properties에서 에러메시지를 관리하려면 application.properties
에 설정을 추가한다.
spring.messages.basename=messages, errors
Spring Bean Validation
스프링의 Bean Validation
은 검증이 필요한 필드에 특정 Annotation을 적용하여 필드가 갖는 스펙을 정의하는 구조로 이루어져있다.
1. Spring Bean Validator
스프링부트는 spring-boot-starter-validation
라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다.LocalValidatorFactoryBean
을 글로벌 Validator로 등록하고, 이 Validator는 @NotNull
같은 Annotation만 보고 검증을 수행한다. 따라서 우리는 @Validated
만 적용하면 된다.
1) @Validated, BindingResult
BindingResult 는 오류 발생시 검증 오류를 보관하는 객체이다. 오류가 발생 시 @Validated Annotation이 자동으로 FieldError , ObjectError 객체를 생성해서 스프링이 제공하는 검증오류를 보관하는 객체인 BindingResult 에 담아준다.
아래 예제에서 중요한것은 검증하고자하는 Member 객체 바로 뒤에 BindingResult bindingResult를 추가하는 것이다.
@PostMapping("/add")
public String addMember(@Validated @ModelAttribute("member") Member member,
BindingResult bindingResult){
if (bindingResult.hasErrors()) {
//오류 발생 시 로직
}
//생략
}
2) FieldError, ObjectError
FieldError는 @NotNull, @Email 과 같은 annotation이 붙은 필드에 annotation으로 지정한 규칙을 지키지 않을때 발생한다.
ObjectError는 필드에만 국한되지 않고 직접만든 어떤 규칙을 지키지 않을때 발생한다.
예를들면 Id, pw 필드가 있는데, 각 필드에 지정된 @NotNull과 같은 규칙은 지켜졌으나 "데이터베이스에 각 Id, pw를 만족하는 멤버객체가 들어있어야한다." 같이 직접만든 규칙을 어겼을때 발생한다.
필드에 오류가 있으면 @Validated가 자동으로 FieldError객체를 생성해서 BindingResult객체에 담아준다.
특정 필드를 넘어서는 오류가 있으면 Object 객체를 생성해서 BindingResult객체에 담아준다.
FieldError 생성자
public FieldError(String objectName, String field, String defaultMessage) {}
ObjectError 생성자
public ObjectError(String objectName, String defaultMessage) {}
String objectName
:@ModelAttribute
이름String field
: 오류가 발생한 필드 이름String defaultMessage
: 오류 메시지
2. 필드 오류
1) 의존관계 추가
Bean Validation을 사용하려면 build.gradle
에 아래와 같은 의존관계를 추가해야 한다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
2) 검증하고자 하는 클래스에 validation annotation 작성
@NotBlank
: 공백 불가@NotNull
: null 값 불가Range
: 범위 설정@Pattern(regexp="")
: 정규식 설정@Email
: 이메일 형식을 맞춰야 함.
@Data
public class MemberSaveForm {
@NotBlank
@Pattern(regexp = "^[가-힣]*$|^[a-zA-Z]*$")
private String name;
@NotNull
@Range(min = 1, max = 120)
private Integer age;
@Pattern(regexp = "\\d{4}\\d{2}\\d{2}")
private String birthday;
@Email
private String email;
}
이외에도 범용적으로 사용되는 검증 방법이 상당히 많다.
검증 Annotation 메뉴얼
https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#validator-defineconstraints-spec
3) 컨트롤러 설정
위에서도 말했듯이 @ModelAttribute
가 적용된 객체 바로 뒤에 BindingResult
가 오는것이 중요하다.
addMember에서 전용 DTO 인 MemberSaveForm form
에 ModelAttribute를 적용했기 때문에,
view에서 "member"라는 이름의 객체로 전달받은것을 인식하도록 @ModelAttribute("member")
로 설정해준다.
bindingResult.hasErrors()
는 validation 실패시 true를 반환한다.
아래 예제의 경우에는 다시 이전 입력 폼으로 돌아가도록 설정했다.
@PostMapping("/add")
public String addMember(@Validated @ModelAttribute("member") MemberSaveForm form,
BindingResult bindingResult){
if (bindingResult.hasErrors()) {
return "basic/addForm"; //오류 발생 시 로직
}
//생략
}
4) 필드 오류 직접 추가
bindingResult.rejectValue("field-name", "errorCode", "defaultMessage");
5) errors.properties 파일 설정
resources
폴더에 errors.properties
파일을 만들고 아래와 같은 값들을 추가한다.
각 필드 오류 발생시 사용할 에러메시지를 설정할 수 있다.
첫째줄을 예로 들면, 에러 발생 시 html의 member
- name
속성에 오류가 발생했고,
해당 필드에 NotBlank
조건에 만족하지 않았을 때,
"이름은 빈칸일 수 없습니다."라는 오류메시지를 출력한다는 의미이다.
NotBlank.member.name=이름은 빈칸일 수 없습니다.
Pattern.member.name=이름은 한글 또는 영어만 입력 가능합니다.
6) view(타임리프)
member.name에 오류 발생 시 "field-error" css를 적용하도록 설정해봤다.th:errorclass="field-error"
<div class="field-error" th:errors="*{name}">이름 입력 오류</div>
th:errorclass
: 에러 발생시 클래스의 th:errorclass로 지정된 클래스를 추가해준다.th:errors
: 해당 필드에 에러 발생시 오류메시지를 표시하도록 설정한다.
<style>
.field-error{
border-color: red;
color: red;
}
</style>
<form action="member.html" th:action th:object="${member}" method="post">
<div>
<label for="name" th:text="#{label.member.name}">이름</label>
<input type="text" id="name" class="form-control"
th:field="*{name}"
th:errorclass="field-error"
th:placeholder="#{order.write.name}" placeholder="이름을 입력하세요">
</div>
<div class="field-error" th:errors="*{name}">이름 입력 오류</div>
</form>
)
3. 글로벌 오류
글로벌 오류는 스프링 Bean Validation을 이용하지 않고 자바 코드로 직접 다루는것이 낫다.
글로벌오류는 bindingResult.rejcet 를 이용한다.
1) 오브젝트 오류 검증 예제
@PostMapping("/login")
public String login(@Validated @ModelAttribute("loginForm") LoginForm form,
BindingResult bindingResult){
//Id,pw 입력받아서 loginService.login 로직 실행 -> 성공 : member 반환 , 실패 : null 반환
Member loginMember = loginService.login(form.getLoginId(),form.getPassword());
//null 반환시 없는 id입니다 글로벌 오류 반환
if(loginMember == null){
log.info("loginMember={}",loginMember);
bindingResult.reject("loginFail");
return "login/loginForm";
}
//생략
}
form 객체에 ObjectError(글로벌 오류) 발생 시bindingResult.reject("loginFail");
로 검증오류를 담아준다.
(1) default메시지를 함께 담을 수도 있다.
bindingResult.reject("loginFail","defaultMessage");
(2) Object[] 배열에 값을 담아서 전달
Object[] 배열에 값을 담아서 검증오류를 전달한 뒤, errors.properties에서배열에 담긴 값을 이용할수도 있다.
bindingResult.reject("loginFail", new Object[]{"kim123123"}, "defaultMessage");
- 0번 인덱스부터
{0}
,{1}
등으로 배열의 담긴 값을 꺼내올 수 있다. - 해당 예제에서 {0}에 담긴 값은 "kim123123" 이다.
2) 에러메시지 출력
bindingResult.reject
의 매개변수
String errorCode
:errors.properties
에서 사용할 이름이다.@Nullable Object errorArgs
: 메시지 출력시 해당 배열에 담긴 값을 사용할 수 있다.String defalutMessage
: default 메시지errors.properties
에 아래 코드를 추가해준다.
loginFail=ID : {0}은 없는 Id 입니다.
new Object[]{"loginId123"}
에 담긴 값을 사용하여
"ID : loginId123은 없는 Id 입니다." 라고 오류메시지를 출력할 수 있다.
3) view에 추가
(글로벌 오류가 여러개인 경우)
th:if="${#fields.hasGlobalErrors()}"
: 글로벌오류(오브젝트오류) 가 있을때 동작한다.th:each="err : ${#fields.globalErrors()}"
globalErrors를 모두 꺼내온다.
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}"
th:text = "${err}">글로벌 오류 메시지</p>
</div>
(글로벌 오류가 하나인 경우)
th:text
를 이용해서 텍스트를 설정해주고,
그 값은 #{loginFail}
을 이용해서 errors.properties에서 꺼내온다.
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:text="#{loginFail}">전체 오류 메시지</p>
</div>
(참고)김영한님 인프런 Spring MVC-2
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2
'JAVA & Spring > Spring' 카테고리의 다른 글
[Spring] 세션, 쿠키, @SessionAttribute 로그인 확인 (0) | 2023.04.21 |
---|---|
[Spring Boot] PRG 패턴 ( Post - Redirect - Get ) (1) | 2023.04.21 |
[Spring Boot] @PathVariable과 @RequestParam - 파라미터 받기 (0) | 2023.04.21 |
[Spring Boot] 스프링 인터셉터 (Spring Interceptor) (0) | 2023.04.21 |
[Spring] 메시지, 국제화 (0) | 2023.04.21 |