로그인 Validator 구현 완료
컨트롤러 내 컨트롤 메서드
/* 로그인 처리하기 */
@PostMapping("/login")
public String login(@Validated @ModelAttribute("user") LoginUserDto user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
/* 로그인 실패시 로그인 폼으로 */
return "home/login";
}
/* 로그인 성공시 홈으로 */
return "redirect:/";
}
Validator 내 validation 메서드
@Override
public void validate(Object target, Errors errors) {
LoginUserDto user = (LoginUserDto) target;
// 필수값 검증
if (!StringUtils.hasText(user.getUsername()) || !StringUtils.hasText(user.getPassword())) {
if (!StringUtils.hasText(user.getUsername())) {
errors.rejectValue("username", "required");
}
if (!StringUtils.hasText(user.getPassword())) {
errors.rejectValue("password", "required");
}
return; // 이후 검증 불필요
}
// 로그인 검증
User loginUser = userService.login(user);
log.debug("Login User: {}", loginUser);
if (loginUser == null) {
errors.reject("loginGlobal", null, null);
} else {
session.setAttribute("user", user);
}
}
User Service 계층에서 로그인을 위한 메서드들
/**
* 로그인 처리를 담은 서비스 계층 메서드
*/
public User login(LoginUserDto user) {
User foundUser = findByUsername(user.getUsername());
return authenticateUser(foundUser, user.getPassword());
}
/**
* 받은 User 객체와 password 파라미터가 일치하는지 확인하고 유저 반환
*
* @param user : User 객체
* @param password : password
* @return : 일치하다면 유저 반환, 아니면 null 반환
*/
private static User authenticateUser(User user, String password) {
if (user != null) {
if (user.getPassword().equals(password)) {
return user;
}
}
return null;
}
/**
* username으로 유저를 찾음
* @param username : String
* @return : User
*/
private User findByUsername(String username) {
return userRepository.findByUsername(username);
}
문제: 서비스 계층에 Validation 로직이 섞여 있다.
서비스 계층에 Validation 로직이 섞여 있어서, 로직이 왔다갔다해서 복잡했다. 서비스 계층과 Validator을 분리할 필요성을 느끼게 됐다.
해결
검증은 validator가, 로그인은 컨트롤러가 하도록 수정하면 된다.
controller
/* 로그인 처리하기 */
@PostMapping("/login")
public String login(HttpSession session, @Validated @ModelAttribute("user") LoginUserDto user, BindingResult bindingResult) {
/* 검증 실행 */
loginValidator.validate(user, bindingResult);
/* 검증에 에러가 발견되면, 폼을 보여줌. */
if (bindingResult.hasErrors()) {
return "home/login";
}
/* 검증이 끝나면, 컨트롤러에서 로그인 처리 */
userService.login(session, user);
/* 로그인 후에 홈으로 리다이렉트 */
return "redirect:/";
}
Validator 내 validation 로직
authenticateUser을 가지고 와서 로직을 최적화했고, 오류 코드는 상수화했다.
...
public static final String REQUIRED_FIELD = "required";
public static final String LOGIN_ERROR = "loginGlobal";
...
@Override
public void validate(Object target, Errors errors) {
LoginUserDto user = (LoginUserDto) target;
String username = user.getUsername();
String password = user.getPassword();
boolean isUsernameEmptyOrBlank = !StringUtils.hasText(username);
boolean isPasswordEmptyOrBlank = !StringUtils.hasText(password);
// 필수값 검증
if (isUsernameEmptyOrBlank || isPasswordEmptyOrBlank) {
if (isUsernameEmptyOrBlank) {
errors.rejectValue("username", REQUIRED_FIELD);
}
if (isPasswordEmptyOrBlank) {
errors.rejectValue("password", REQUIRED_FIELD);
}
return; // 이후 검증 불필요
}
// 로그인 검증
User foundUser = getFoundUser(user);
if (rejectAuthentication(foundUser, password)) {
errors.reject(LOGIN_ERROR, null, null);
}
}
private User getFoundUser(LoginUserDto user) {
return userService.findByUsername(user.getUsername());
}
/**
* 받은 User 객체와 password 파라미터가 일치하는지 확인하고 실패할 시 결과 반환
*
* @param user : User 객체
* @param password : password
* @return : 일치하다면 true, 일치하지 않으면 false
*/
private boolean rejectAuthentication(User user, String password) {
if (user == null) {
return true;
}
return !user.getPassword().equals(password);
}
Service 클래스의 로그인 로직, 매우 단순화되었다.
/**
* 로그인 처리를 담은 서비스 계층 메서드
* Validator에서 호출한다.
*
* @param session : 세션
* @param loginUserDto : Valitation에 성공한 유저Dto
*/
public void login(HttpSession session, LoginUserDto loginUserDto) {
User user = findByUsername(loginUserDto.getUsername());
session.setAttribute("user", user);
}