비밀번호 초기화 페이지

This commit is contained in:
현성필
2024-05-27 11:15:45 +09:00
parent 9b53b9272c
commit c79b4c5535
13 changed files with 772 additions and 153 deletions
@@ -0,0 +1,108 @@
package com.eactive.testmaster.login.controller;
import com.eactive.testmaster.common.exception.UserNotFoundException;
import com.eactive.testmaster.login.dto.PasswordResetDTO;
import com.eactive.testmaster.login.entity.Token;
import com.eactive.testmaster.login.service.TokenService;
import com.eactive.testmaster.user.service.UserService;
import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Created by Sungpil Hyun
*/
@Controller
@ConditionalOnProperty(name = "password.reset.enabled", havingValue = "true")
public class PasswordResetController {
public static final String RESET_PASSWORD = "page/resetPassword";
public static final String FORGOT_PASSWORD = "page/forgotPassword";
@Autowired
private UserService userService;
@Autowired
private TokenService tokenService;
@Autowired
private Validator validator;
@GetMapping(value = "/forgot_password.do")
public String forgotPasswordView() {
return FORGOT_PASSWORD;
}
/**
* 비밀번호 초기화 링크를 생성한다.
*
* @param email
* @param model
* @return
*/
@PostMapping(value = "/forgot_password.do")
public String requestPasswordReset(@RequestParam(value = "email", defaultValue = "0") String email,
Model model) {
try {
userService.requestPasswordReset(email);
} catch (UserNotFoundException e) {
model.addAttribute("error", "존재하지 않는 이메일입니다.");
return FORGOT_PASSWORD;
} catch (Exception e) {
model.addAttribute("error", "비밀번호 초기화 요청에 실패하였습니다.");
return FORGOT_PASSWORD;
}
return "redirect:/forgot_password.do?success";
}
@GetMapping("/reset_password.do")
public String resetPasswordView(@RequestParam(value = "token", defaultValue = "0") String tokenValue,
Model model) {
Token token = tokenService.findByToken(tokenValue);
if (token == null || token.getExpiresAt().isBefore(LocalDateTime.now())) {
return "redirect:/forgot_password.do?expired";
}
model.addAttribute("tokenValue", tokenValue);
model.addAttribute("passwordReset", new PasswordResetDTO());
return RESET_PASSWORD;
}
@PostMapping("/reset_password.do")
@Transactional
public String resetPassword(@ModelAttribute("passwordResetRequest") PasswordResetDTO passwordReset,
BindingResult bindingResult,
Model model) {
Token token = tokenService.findByToken(passwordReset.getToken());
if (token == null || token.getExpiresAt().isBefore(LocalDateTime.now())) {
return "redirect:/forgot_password.do?expired";
}
validator.validate(passwordReset, bindingResult);
if (bindingResult.hasErrors()) {
model.addAttribute("tokenValue", passwordReset.getToken());
model.addAttribute("passwordReset", passwordReset);
return RESET_PASSWORD;
}
userService.resetPassword(token.getEmail(), passwordReset.getPassword());
tokenService.deleteToken(token);
return "redirect:/login";
}
}
@@ -0,0 +1,36 @@
package com.eactive.testmaster.login.dto;
import com.eactive.testmaster.common.validator.PasswordMatch;
import java.io.Serializable;
@PasswordMatch(input = "password", confirm = "password2")
public class PasswordResetDTO implements Serializable {
private String token;
private String password;
private String password2;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword2() {
return password2;
}
public void setPassword2(String password2) {
this.password2 = password2;
}
}
@@ -0,0 +1,54 @@
package com.eactive.testmaster.login.entity;
import java.io.Serializable;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "PT_TOKEN")
public class Token implements Serializable {
public Token(String tokenValue, String email, LocalDateTime expiresAt) {
this.tokenValue = tokenValue;
this.email = email;
this.expiresAt = expiresAt;
}
public Token() {
}
@Id
@Column(name = "TOKEN")
private String tokenValue;
private String email;
private LocalDateTime expiresAt;
public String getTokenValue() {
return tokenValue;
}
public void setTokenValue(String tokenValue) {
this.tokenValue = tokenValue;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public LocalDateTime getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(LocalDateTime expiresAt) {
this.expiresAt = expiresAt;
}
}
@@ -0,0 +1,7 @@
package com.eactive.testmaster.login.repository;
import com.eactive.testmaster.login.entity.Token;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TokenRepository extends JpaRepository<Token, String> {
}
@@ -0,0 +1,32 @@
package com.eactive.testmaster.login.service;
import com.eactive.testmaster.login.entity.Token;
import com.eactive.testmaster.login.repository.TokenRepository;
import java.time.LocalDateTime;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class TokenService {
@Autowired
private TokenRepository tokenRepository;
public Token createToken(String email) {
String token = UUID.randomUUID().toString();
LocalDateTime expiresAt = LocalDateTime.now().plusHours(24);
Token tokenObj = new Token(token, email, expiresAt);
return tokenRepository.save(tokenObj);
}
public Token findByToken(String token) {
return tokenRepository.findById(token).orElse(null);
}
public void deleteToken(Token token) {
tokenRepository.delete(token);
}
}
@@ -1,6 +1,7 @@
package com.eactive.testmaster.user.controller;
import com.eactive.testmaster.common.util.SecurityUtil;
import com.eactive.testmaster.user.dto.StaffPasswordChangeRequestDTO;
import com.eactive.testmaster.user.dto.UserRegisterDTO;
import com.eactive.testmaster.user.dto.UserSearch;
import com.eactive.testmaster.user.dto.UserUpdateDTO;
@@ -25,8 +26,9 @@ import org.springframework.web.bind.annotation.RequestParam;
@RequestMapping("/mgmt/users")
public class UserMgmtController {
public static final String STAFF_USER_EDIT = "page/users/staffUserEdit";
public static final String USERS_LIST_VIEW = "redirect:/mgmt/users/list_view.do";
private static final String STAFF_USER_EDIT = "page/users/staffUserEdit";
private static final String USERS_LIST_VIEW = "redirect:/mgmt/users/list_view.do";
private static final String STAFF_PASSWORD_RESET_VIEW = "page/users/staffPasswordReset";
private final StaffUserService staffUserService;
@@ -129,4 +131,23 @@ public class UserMgmtController {
return USERS_LIST_VIEW;
}
@PostMapping("/staff_password_reset_view.do")
public String staffPasswordReset(@ModelAttribute("passwordChangeRequest") StaffPasswordChangeRequestDTO passwordChangeRequestDTO) {
StaffUser found = staffUserService.findById(passwordChangeRequestDTO.getId());
passwordChangeRequestDTO.setUsername(found.getUsername());
return STAFF_PASSWORD_RESET_VIEW;
}
@PostMapping("/update_staff_password.do")
public String updateStaffPassword(@ModelAttribute("passwordChangeRequest") StaffPasswordChangeRequestDTO passwordChangeRequestDTO,
BindingResult bindingResult) {
validator.validate(passwordChangeRequestDTO, bindingResult);
if (bindingResult.hasErrors()) {
return STAFF_PASSWORD_RESET_VIEW;
}
staffUserService.updatePassword(passwordChangeRequestDTO.getId(), passwordChangeRequestDTO.getNewPassword());
return USERS_LIST_VIEW;
}
}
@@ -0,0 +1,51 @@
package com.eactive.testmaster.user.dto;
import com.eactive.testmaster.common.validator.PasswordMatch;
import javax.validation.constraints.NotEmpty;
@PasswordMatch(input = "newPassword", confirm = "newPassword2")
public class StaffPasswordChangeRequestDTO {
@NotEmpty(message = "{field.required}")
private String id;
private String username;
@NotEmpty(message = "{field.required}")
private String newPassword;
@NotEmpty(message = "{field.required}")
private String newPassword2;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
public String getNewPassword2() {
return newPassword2;
}
public void setNewPassword2(String newPassword2) {
this.newPassword2 = newPassword2;
}
}
@@ -62,7 +62,15 @@ public class StaffUserService {
staffUserRepository.save(user);
}
public void updatePassword(String id, String newPassword) {
StaffUser user = staffUserRepository.findById(id).orElseThrow(() -> new UserNotFoundException(USER_NOT_FOUND_MESSAGE));
user.setUserSecurity(newPassword);
user.setLockAt(EnabledStatus.N);
user.setLockCnt(0);
user.setLastLockDate(null);
staffUserRepository.save(user);
}
}
@@ -3,10 +3,17 @@ package com.eactive.testmaster.user.service;
import com.eactive.testmaster.common.entity.EnabledStatus;
import com.eactive.testmaster.common.exception.UserNotFoundException;
import com.eactive.testmaster.common.util.EncryptionUtil;
import com.eactive.testmaster.login.entity.Token;
import com.eactive.testmaster.login.service.TokenService;
import com.eactive.testmaster.user.entity.StaffUser;
import com.eactive.testmaster.user.entity.User;
import com.eactive.testmaster.user.repository.StaffUserRepository;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -17,14 +24,23 @@ import org.springframework.stereotype.Service;
//@Transactional(propagation = Propagation.NESTED)
public class UserService implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public static final String USER_NOT_FOUND_MESSAGE = "사용자 정보가 존재하지 않습니다.";
@Autowired
StaffUserRepository staffUserRepository;
private final StaffUserRepository staffUserRepository;
@Autowired
private EncryptionUtil encryptionUtil;
private final TokenService tokenService;
public UserService(StaffUserRepository staffUserRepository, EncryptionUtil encryptionUtil, TokenService tokenService) {
this.staffUserRepository = staffUserRepository;
this.encryptionUtil = encryptionUtil;
this.tokenService = tokenService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
@@ -65,6 +81,25 @@ public class UserService implements UserDetailsService {
staffUserRepository.save((StaffUser) user);
}
public void requestPasswordReset(String email) {
Token token = tokenService.createToken(email);
Map<String, Object> params = new HashMap<>();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = token.getExpiresAt().format(formatter);
params.put("userId", email);
params.put("token", token.getTokenValue());
params.put("expiresAt", formattedDateTime);
logger.info("requestPasswordReset::params= {}", params);
}
public void resetPassword(String email, String newPassword) {
User user = findByUserId(email);
updatePassword(user, newPassword);
}
public void updateUser(User user) {
staffUserRepository.save((StaffUser) user);
}