사용 기술
Spring Boot + MVC + postgreSQL + ajax
+ jQuery Validation + Hibernate validator + JPA
기본적인 환경설정이나 버전에 대한 내용은 생략하고 코드에 대한 설명과 사용한 기술에 대해 설명할 것임
유저 및 힌트 테이블 모델 정의
회원 가입에 필요한 테이블은 유저 테이블과 힌트 테이블이다. 힌트 테이블이 필요한 이유는 비밀번호 찾기 시 필요한 힌트에 대한 답변을 유저가 가지고 있고, 답변에 대한 질문이 무엇인지 알기 위해서이다.
요구 사항에 따라서 Hibernate Validator를 사용하였다.
package com.insilicogen.CRUD_PRJ.user.service;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; // Hibernate Validator 인터페이스 : jakarta
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
@Table(name = "User_TBL")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer userSn;
@Column(name = "USER_LGN_ID", length = 30)
@NotNull
private String userLoginId;
@Column(name = "USER_NM", length = 30)
@NotNull
private String userNm;
@Column(name = "PSWD", length = 30)
@NotNull
@NotBlank
@Size(min=8)
private String password;
@Column(name = "SEXDSTN", length = 1)
private Character sex;
@Column(name = "BRTH_YMD")
private Date dateOfBirth;
@Column(name = "HINT_CNSR", length = 150)
@NotNull
private String hintAnswer;
@ManyToOne
@JoinColumn(name = "PSWD_HINT_SN")
private PSWD_HINT pswdHintSn;
}
User 엔터티를 설정
package com.insilicogen.CRUD_PRJ.user.service;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@Table(name = "PSWD_HINT")
public class PSWD_HINT {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "PSWD_HINT_SN")
private Integer pswdHintSn;
@Column(name = "HINT_CN", length = 300)
@NotNull
private String hintCn;
public PSWD_HINT() {
//
}
public PSWD_HINT(Integer pswdHintSn, String hintCn) {
this.pswdHintSn = pswdHintSn;
this.hintCn = hintCn;
}
}
Hint 엔터티 설정
@NotNull 과 @NotBlank , @Size 등의 어노테이션은 Hibernate Validator에서 제공하는 어노테이션이다.
Hibernate Validator
의존성 추가
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
Hibernate Validator 주된 어노테이션
어노테이션 | 설명 | 주요 속성 |
@NotNull | 필드가 null이 아닌지 확인합니다. | |
@Size | 문자열, 컬렉션 또는 배열의 크기를 확인합니다. | min, max, message |
@Pattern | 값이 지정된 정규식과 일치하는지 확인합니다. | regexp, message |
값이 올바른 형식의 이메일 주소인지 확인합니다. | ||
@Min | 숫자 값이 지정된 최소값보다 크거나 같은지 확인합니다. | value, message |
@Max | 숫자 값이 지정된 최대값보다 작거나 같은지 확인합니다. | value, message |
@NotEmpty | 문자열, 컬렉션 또는 배열이 비어 있지 않은지 확인합니다. | |
@NotBlank | 문자열이 공백이 아니라 비어 있지 않은지 확인합니다. |
사용법
필요한 Controller에서 @Valid와 @RequestBody를 함께 사용
회원가입 컨트롤러
@GetMapping("/user/insertUser.do")
public String insertUser(Model model) {
System.out.println("insertUser 컨트롤러 진입");
List<PSWD_HINT> hintQuestions = hintRepository.findAll();
model.addAttribute("hintQuestions", hintQuestions);
return "/user/insertUser";
}
- "/user/insertUser.do" url을 통해서 리턴 값인 "/user/insertUser" 를 찾아가 user 폴더 내 insertUser.jsp를 선택
- 힌트 테이블에 저장된 내용을 "/user/insertUser.do" url을 통해서 전달
insertUser.jsp
컨트롤러가 찾은 insertUser.jsp에서는 아래의 코드로 화면을 구성한다.
<h2>회원가입 폼</h2>
<form id="registerForm" method="POST">
<label for="login_id">로그인 ID:</label> <input type="text" id="login_id"name="userLoginId" required>
<br><br>
<label for="user_name">사용자 이름:</label> <input type="text" id="user_name" name="userNm" required>
<br><br>
<label for="password">비밀번호:</label> <input type="password" id="password" name="password" required>
<br><br>
<label for="sex">성별:</label> <select id="sex" name="sex">
<option value="M">남성</option>
<option value="F">여성</option>
</select>
<br><br>
<label for="birth">생년월일:</label> <input type="date" id="birth" name="dateOfBirth" required>
<br><br>
<select id="hint_question" name="hintQuestion">
<c:forEach var="hintQuestion" items="${hintQuestions}" >
<option value="${hintQuestion.pswdHintSn} , ${hintQuestion.hintCn}">${hintQuestion.hintCn}</option>
</c:forEach>
</select>
<br><br>
<label for="hint_comment">비밀번호 힌트 답변 : </label> <input type="text" id="hint_comment" name="hintAnswer">
<br><br> <!-- <input type="submit" value="가입하기"> -->
<input type="button" id="submitBtn" value="가입하기">
</form>
실행된 화면은 아래와 같다.
각각의 텍스트를 입력하고 가입하기 버튼을 누르면 해당 정보가 유저 데이터베이스에 저장된다.
로그인 ID와 비밀번호에 대한 유효성 검사를 우선적으로 jQuery Validation을 이용하여 진행한다.
$(document).ready(function() {
$('#registerForm').validate({
rules: {
userLoginId: {
required: true,
minlength: 2,
remote: {
url: '/user/checkDuplicateId',
type: 'POST',
data: {
userLoginId: function() {
return $('#login_id').val();
}
}
}
},
password: {
required: true,
minlength: 8,
remote: {
url: '/user/checkPassWord',
type: 'POST',
data: {
password: function() {
return $('#password').val();
}
}
}
}
},
messages: {
userLoginId: {
remote: '이미 사용 중인 아이디입니다.',
minlength: '아이디는 최소 2자 이상이어야 합니다.'
},
password: {
remote: '비밀번호는 숫자, 영문, 특수문자를 포함하여 최소 8자리 이상이어야 합니다.',
minlength: '비밀번호는 최소 8자 이상이어야 합니다.'
}
},
submitHandler: function(form){
console.log("submit!");
console.log(form);
form.submit(); // 폼 제출
}
});
폼 제출이 일어나면 rules 옵션을 통해 userLoginId와 password를 검사한다. form 입력 데이터에서 허용된 값과 허용되지 않은 값을 미리 파악하고 방지하기 위해 사용한다. jQuery validation은 유효성 검사를 도와주는 플러그인 이라 생각해도 좋다.
jquery validate를 로컬에 다운 받아<script src="../resources/js/jquery/jquery.validate.js"></script> 로 플러그인을 설정한다.
기본적으로 검증되는 메소드는 아래와 같다.
- required : 필수 입력 엘리먼트입니다.
- remote : 엘리먼트의 검증을 지정된 다른 자원에 ajax 로 요청합니다.
- minlength : 최소 길이를 지정합니다.
- maxlength : 최대 길이를 지정합니다.
- rangelength : 길이의 범위를 지정합니다.
- min : 최소값을 지정합니다.
- max : 최대값을 지정합니다.
- range : 값의 범위를 지정합니다.
- step : 주어진 단계의 값을 가지도록 합니다.
- email : 이메일 주소형식을 가지도록 합니다.
- url : url 형식을 가지도록 합니다.
- date : 날짜 형식을 가지도록 합니다.
- dateISO : ISO 날짜 형식을 가지도록 합니다.
- number : 10진수를 가지도록 합니다.
- digits : 숫자 형식을 가지도록 합니다.
- equalTo : 엘리먼트가 다른 엘리먼트와 동일해야 합니다.
만약 비밀번호가 8자 이하라면 어떻게 될까?
위와 같이 message 옵션으로 설정한 minlength 유효성 검사에 걸려 위와 같이 출력한다.
프론트 단에서 검사한다고 끝나는게 아니라 백 단에서도 유효성 검증이 필요하다.
$('#login_id').blur(function() { // 입력 필드에 포커스가 사라질 때마다 해당 함수 내의 코드를 실행하도록 설정
var loginId = $(this).val(); // 입력된 아이디 값
var password = $('#password').val(); // 비밀번호 값
$.post('/user/checkDuplicateId', { userLoginId: loginId }, function(duplicateResponse) {
$.post('/user/checkPassWord', { password: password }, function(passwordResponse) {
if (!duplicateResponse || !passwordResponse) {
$('#submitBtn').prop('disabled', true); // 가입 버튼 비활성화
} else {
$('#submitBtn').prop('disabled', false); // 가입 버튼 활성화
}
});
});
});
ajax의 post 메소드를 이용하여 입력된 필드의 값을 url을 통해 컨트롤러에 보내 컨트롤러에서 유효성을 한 번더 검사한다.
checkDuplicateId 컨트롤러
@PostMapping("/user/checkDuplicateId")
@ResponseBody
public boolean checkDuplicateId(@RequestParam("userLoginId") String userLoginId) {
System.out.println("중복 검사 컨트롤러 진입");
boolean isDuplication = userService.isUserIdDuplicated(userLoginId);
if (isDuplication)
return false; // 중복
else
return true; // 유효한 아이디
}
파라미터로 넘어오는 userLoginId를 사용하여 서비스 단에서 검사 로직을 구현하여 검증한다. 반환 값을 true / false로 지정하여 id 중복과 pw 통과의 검사가 동시에 true 값을 가져야만 버튼이 활성화 되도록 했다.
isUserIdDuplicated 함수 (중복 로그인 id 검사 함수)
public boolean isUserIdDuplicated(String lgnId) { // 중복 검사 로직
User user = userRepository.findByUserLoginId(lgnId);
return user != null;
}
JPA의 findByUserLoginId 함수로 User 테이블 내 동일 로그인id가 존재하는지 검사하고 존재하는 경우에 true, 존재하지 않는 경우에 false를 반환한다.
다시 중복 검사 컨트롤러에서 조건문을 살펴보자.
해당 로직은 isDuplication이 true를 반환한다면 즉, 입력된 id값이 유저 테이블에 존재한다면 중복을 의미하므로 false를 반환하도록 한다. 그게 아니라면 중복되지 않은 유요한 아이디 이므로 true를 반환한다.
checkPassWord 컨트롤러
@PostMapping("/user/checkPassWord")
@ResponseBody
public boolean checkPassWord(@RequestParam("password") String password) {
System.out.println("비밀번호 검사 컨트롤러 진입");
Pattern pass = Pattern.compile("^(?=.*[a-zA-Z])(?=.*\\d)(?=.*\\W).{8,20}$");
Matcher pm = pass.matcher(password);
if (!pm.find())
return false;
else
return true;
}
비밀번호에 대한 검사는 Pattern 클래스와 Matcher 클래스를 사용하였다.
Pattern.compile() 메소드로 정규표현식으로부터 패턴을 생성한다.
pass.matcher() 메소드로 패턴을 타겟 스트링과 매치 시킨다.
pm.find로 패턴에 맞는 값을 하나 씩 찾고 맞지 않는 경우 false를 반환하여 검증한다.
jQuery validation은 잘 작동하는 것을 확인할 수 있다.
유효성 검증이 끝나면 회원 가입 폼을 제출한다.
$('#submitBtn').click(function(event) {
event.preventDefault();
if ($('#registerForm').valid()) {
var userBodyData = {
userLoginId: $('#login_id').val(),
userNm: $('#user_name').val(),
password: $('#password').val(),
sex: $('#sex').val(),
dateOfBirth: $('#birth').val(),
hintAnswer: $('#hint_comment').val()
};
var hintValue = $('#hint_question').val();
var hintValues = hintValue.split(',');
var hintParamData = {
pswdHintSn: hintValues[0],
hintCn: hintValues[1]
};
var formData = {
body: userBodyData,
param: hintParamData
};
$.ajax({
type: 'POST',
url: "/user/insertUser.json",
contentType: 'application/json',
data: JSON.stringify(formData),
success: function(response) {
console.log(response);
alert('회원가입이 완료되었습니다.');
location.href = '/home';
},
error: function(xhr, status, error) {
console.log(xhr.responseText);
alert('회원가입에 실패했습니다. 다시 시도해주세요.');
}
});
}
});
유저 정보는 힌트 테이블과 외래키로 묶여있기 때문에 선택한 힌트에 대한 정보도 함께 가져와야 한다.
설정한 "/user/insertUser.json" url로 컨트롤러에 데이터를 body 값으로 보낸다.
insertUser 컨트롤러
@PostMapping("/user/insertUser.json")
@ResponseBody
public User insertUserJSON(@RequestBody @Valid UserFormDto formData) {
System.out.println("insertUserJSON 컨트롤러 진입");
UserDto userDto = formData.getBody();
Integer pswdHintSn = formData.getParam().getPswdHintSn();
String pswdHintCn = formData.getParam().getHintCn();
PSWD_HINT pwsdHint = new PSWD_HINT(pswdHintSn, pswdHintCn);
User registUser = userService.registerUser(userDto, pwsdHint);
return registUser;
}
formData에 저장된 값으로 회원 등록 로직을 실행한다.
public User registerUser(UserDto userDto, PSWD_HINT pswdHint) {
User user = new User();
user.setUserLoginId(userDto.getUserLoginId());
user.setUserNm(userDto.getUserNm());
user.setPassword(userDto.getPassword());
user.setSex(userDto.getSex());
user.setDateOfBirth(userDto.getDateOfBirth());
user.setHintAnswer(userDto.getHintAnswer());
user.setPswdHintSn(pswdHint);
// UserRepository를 사용하여 User 엔티티를 저장
this.userRepository.save(user);
System.out.println("유저 정보 db 저장 완료");
return user;
}
이제 텍스트에 값을 넣어 가입하기를 눌러보자.
회원 가입이 정상적으로 이루어진다.
'인실리코젠' 카테고리의 다른 글
[인실리코젠] Spring CRUD - 게시글 조회 구현 중 겪은 트러블 슈팅 (0) | 2024.06.17 |
---|---|
[인실리코젠] Spring Security + JWT + JPA (0) | 2024.03.15 |
[인실리코젠] Spring CRUD 프로젝트 요구 사항 분석 및 데이터베이스 설계 (1) | 2024.02.16 |
[인실리코젠] Web Crawling - Finish (1) | 2024.02.02 |
[인실리코젠] WebCrawling - 2 (0) | 2024.01.22 |