* 폼에서 submit 이벤트 발생시 폼입력값을 검증하는 것
* 폼 입력값을 저장하는 Form 객체에 각 멤버변수별로 입력값의 유효성응ㄹ 체크하는 어노테이션을 추가한다.
* 컨트롤러의 요청핸들러 메소드에서 @Valid 어노테이션을 이용해서 Form객체에 저장된 폼 입력값의 유효성을 검사한다.
* 폼 입력값 유효성 검증 절차
1. 폼 입력값 유효성 체크를 지원하는 라이브러리 의존성을 추가한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
hibernate-validate라는 라이브러리가 추가된다.
validation-api = 표준이 정해져 있다.
hibernate-validate = 표준의 구현 중 하나다.
* javax.validation.constraints에는 여러 어노테이션들이 정의되어 있다.
Ex) Email / NotBlank / Future / Past 등 필수로 들어갔으면 하는 어노테이션이 정의되어 있고 필요한 것을 추가하면 된다.
2. 입력폼에서 spring의 form 태그를 사용해서 입력폼과 입력필드를 구성한다.
* form 태그 = <form:form>태그, <form:input>태그, <form:password>태그, <form:errors>태그 등을 이용해서 입력폼과 입력필드를 구성한다.
3. 폼 입력값을 저장하는 Form 클래스의 멤버면수에 유효성 체크 규칙을 어노테이션을 이용해서 지정한다.
@NotNull, @NotEmpty, @NotBlank, @Email 등의 어노테이션을 이용해서 유효성 체크 규칙을 멤버변수별로 지정한다.
4. 요청핸들러 메소드에서 Form 객체에 저장된 폼 입력값에 대한 유효성 검사를 수행하고, 검사결과를 전달받기
@PostMapping("/register")
public String saveUser(@Valid UserRegisterForm userRegisterForm, BindingResult errors) {
if(errors.hasErrors()) {
// 유효성 검사를 통과하지 못한 경우(true가 나올 경우), 입력화면으로 내부이동시킨다.
return"register-form"; //WEB-INF/views/register-form.jsp로 내부이동시킨다.
}
// 유효성 검사를 통과한 경우(false가 나올 경우),회원가입 업무로직을 호출한다.
userService.registerUser(userRegisterForm);
}
* @Valid 어노테이션은 UserRegisterForm 객체에 저장된 폼 입력값에 대한 유효성 검사를 수행하게 하는 어노테이션이다.
* userRegisterForm 과 BindingResult 사이에는 무엇이든 있으면 안된다.
* BindingResult객체는 유효성 검사결과를 전달받는 객체다.
폼입력값 검증에 활용되는 어노테이션
@NotNull
null 값을 허용하지 않는다.
@NotEmpty
null 값을 허용하지 않으며, 최소 한 개 이상의 글자를 포함해야 한다.
@NotBlank
null 값을 허용하지 않으며, 최소 한 개 이상의 글자(공백문자를 제외한)를 포함해야 한다.
@Size(min=숫자, max=숫자)
문자열 혹은 배열의 최소 길이, 최대 길이를 지정한다.
@Length(min=숫자, max=숫자)
문자열의 최소 길이, 최대 길이를 지정한다.
@Min
최소 정수값을 지정한다.
@Max
최대 정수값을 지정한다.
@Pattern(regexp=정규표현식, flag={"i", "g", "m"})
문자열이 지정된 정규표현식과 일치해야 한다.
@Email
문자열이 이메일 형식인지 체크한다.
@URL
문자열이 URL 형식인지 체크한다.
@Past
날짜가 현재시간보다 과거인지 체크한다.
@Future
날짜가 현재시간보다 미래인지 체크한다.
register-fom.jsp
<form:form modelAttribute="userRegisterForm" id="form-register" class="border bg-light p-3" method="post" action="register">
<div class="mb-3">
<label class="form-label">접속 권한</label>
<div>
<div class="form-check form-check-inline">
<form:checkbox class="form-check-input" path="roleName" value="ROLE_GUEST" />
<label class="form-check-label">게스트</label>
</div>
<div class="form-check form-check-inline">
<form:checkbox class="form-check-input" path="roleName" value="ROLE_USER" />
<label class="form-check-label">사용자</label>
</div>
</div>
<form:errors path="roleName" cssClass="text-danger"/>
</div>
<div class="mb-3">
<label class="form-label">아이디</label>
<form:input class="form-control form-control-sm" path="id" />
<form:errors path="id" cssClass="text-danger"/>
</div>
<div class="mb-3">
<label class="form-label">비밀번호</label>
<form:password class="form-control form-control-sm" path="password" />
<form:errors path="password" cssClass="text-danger"/>
</div>
<div class="mb-3">
<label class="form-label">이름</label>
<form:input class="form-control form-control-sm" path="name" />
<form:errors path="name" cssClass="text-danger"/>
</div>
<div class="mb-3">
<label class="form-label">이메일</label>
<form:input class="form-control form-control-sm" path="email" />
<form:errors path="email" cssClass="text-danger"/>
</div>
<div class="mb-3">
<label class="form-label">전화번호</label>
<form:input class="form-control form-control-sm" path="tel" />
<form:errors path="tel" cssClass="text-danger"/>
</div>
<div class="text-end">
<a href="/home" class="btn btn-secondary btn-sm">취소</a>
<button type="submit" class="btn btn-primary btn-sm">가입</button>
</div>
</form:form>
HomeController
// 회원가입 요청
@PostMapping("/register")
public String register(@ModelAttribute("userRegisterForm") @Valid UserRegisterForm userRegisterForm, BindingResult errors) {
//@Valid는 유효성 체크를 한다.(이 객체에 저장되어 있는 폼 입력값이 전달되면 유효성 체크를 한다.) / BindingResult는 유효성 검사 결과가 저장되는 객체다.
if(errors.hasErrors()) { // 유효성 체크를 통과하지 못하면(true가 나오면), register-form을 반환한다.
System.out.println(errors);
return "register-form";
}
try {
userService.registerUser(userRegisterForm);
} catch (AleradeyRegisteredUserIdException ex) {
errors.rejectValue("id", null, "이미 사용중인 아이디입니다.");
return "register-form";
} catch (AleradeyRegisteredEmailException ex) {
errors.rejectValue("email", null, "이미 사용중인 이메일입니다.");
return "register-form";
}
return "redirect:registered";
}
UserRegisterForm.java
public class UserRegisterForm {
@Size(min = 1, message = "접속 권한은 하나 이상 체크하세요.")
private List<String> roleName;
@NotBlank(message = "아이디는 필수입력값입니다.") // 공백일 때 표시될 문자
@Length(min = 4, max = 20, message="아이디는 4글자이상 20글자 이하로 입력하세요.") // 최소 4글자, 최대 20글자
private String id;
@NotBlank(message = "비밀번호는 필수입력값입니다.")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$", message = "비밀번호는 최소 8 자, 대문자 하나 이상, 소문자 하나 및 숫자 하나 이상 포함해야 합니다.")
private String password;
@NotBlank(message = "이름은 필수입력값입니다.") // 공백일 때 표시될 문자
@Pattern(regexp = "^[가-힣]{2,}$", message = "이름을 한글 2글자 이상으로 입력하세요.")
private String name;
@NotBlank(message = "이메일은 필수입력값입니다.") // 공백일 때 표시될 문자
@Email(message = "유효한 이메일 형식이 아닙니다.")
private String email;
@NotBlank(message = "전화번호는 필수입력값입니다.") // 공백일 때 표시될 문자
@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "유효한 전화번호 형식이 아닙니다.") // "\\는 숫자를 의미한다."
private String tel;
}
AleradeyRegisteredUserIdException.java
public class AleradeyRegisteredUserIdException extends ApplicationException
private static final long serialVersionUID = 6006220286040629384L;
public AleradeyRegisteredUserIdException(String message) {
super(message);
}
}
AleradeyRegisteredEmailException.java
public class AleradeyRegisteredEmailException extends ApplicationException {
private static final long serialVersionUID = 8098332857060144740L;
public AleradeyRegisteredEmailException(String message) {
super(message);
}
}
UserService.java
public void registerUser(UserRegisterForm userRegisterForm) {
User savedUser = userMapper.getUserById(userRegisterForm.getId());
if (savedUser != null) {
throw new AleradeyRegisteredUserIdException("["+userRegisterForm.getId()+"] 사용할 수 없는 아이디입니다.");
}
savedUser = userMapper.getUserByEmail(userRegisterForm.getEmail());
if (savedUser != null) {
throw new AleradeyRegisteredEmailException("["+userRegisterForm.getEmail()+"] 사용할 수 없는 이메일입니다.");
}
실행결과(지정된 조건에 맞지 않을 경우)
ModelAttribute
- model에 userRegisterForm이라는 것을 만들어 값을 담아놓는다.
- modelAttribute를 통해 userRegisterForm를 토대로 입력필드를 채운다.
- path = 입력필드의 이름이다. (name과 id가 path와 같은 이름으로 지정된다.)
CSRF
loginform.jsp
<div class="row mb-3">
<div class="col-12">
<p>아이디와 비밀번호를 입력하고 로그인 버튼을 클릭하세요</p>
<form id="form-register" class="border bg-light p-3" method="post" action="login">
<!-- csrf 토큰값을 히든필드로 추가한다. -->
<sec:csrfInput />
<div class="mb-3">
<label class="form-label">아이디</label>
<input type="text" class="form-control form-control-sm" name="id" />
</div>
<div class="mb-3">
<label class="form-label">비밀번호</label>
<input type="password" class="form-control form-control-sm" name="password" />
</div>
<div class="text-end">
<a href="/home" class="btn btn-secondary btn-sm">취소</a>
<button type="submit" class="btn btn-primary btn-sm">로그인</button>
</div>
</form>
</div>
</div>
navbar.jsp
<li class="nav-item"><a class="nav-link" href="/logout" onclick="logout(event)">로그아웃</a></li>
<form id="form-logout" method="post" action="logout">
<sec:csrfInput />
</form>
<script>
function logout(event) {
event.preventDefault();
document.getElementById("form-logout").submit();
}
</script>