개발자 테스트 체크리스트를 만들다가 전 프로젝트랑 비교했을 때, 내가 회원 아이디 찾기, 비밀번호 찾기를 구현하지 않았다는걸 알게 되었다. 그래서 부랴부랴 만들었다. 이메일인증을 이미 구현했기 때문에, 이메일을 이용한 방법을 사용해보기로 했다.
ㅇ 간단한 프로세스(아이디 찾기)
api/v1/users/me/id api를 호출하면 회원가입로직처럼 인증번호를 만들어서 이메일로 보내줌.
api/v1/users/me/confirm-id api를 호출해서 인증번호를 검증하고, db에서 아이디 찾아서 이메일로 보내줌.
ㅇ 간단한 프로세스(비밀번호 찾기)
아이디 찾기와 마찬가지로
api/v1/users/me/pw api 호출해서 인증번호 보냄.
api/v1/users/me/confirm-pw api 호출해서 인증번호,id 검증하고 새롭게 만든 비밀번호 이메일로 보내줌.
ㅇ 코드(아이디찾기)
api/v1/users/me/id
ㅇ 컨트롤러
/*
회원 아이디 찾기(이메일로 인증번호 전송)
*/
@PostMapping("/me/id")
public ResponseEntity<?> meId(@Valid @RequestBody UserDto.MeEmail requestBody) {
try {
emailService.sendEmailWithTokenId(requestBody.getEmail(),"[KnockKnock] 회원 아이디 찾기 요청 이메일입니다.");
return ResponseEntity.ok("이메일이 성공적으로 발송되었습니다.");
} catch (MessagingException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("이메일 발송에 실패했습니다.");
}
}
ㅇ DTO
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class MeEmail {
@NotNull(message = "Null 값은 입력할 수 없습니다.")
@NotBlank
@Email
private String email;
}
ㅇ 서비스
public void sendEmailWithTokenId(String recipientEmail, String subject) throws MessagingException {
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(recipientEmail);
helper.setSubject(subject);
String randomKey = generateRandomKey(7);
// EmailConfirmRandomKey 저장
EmailConfirmRandomKey confirmRandomKey = EmailConfirmRandomKey.builder()
.email(recipientEmail)
.randomKey(randomKey)
.build();
emailConfirmRandomKeyRepository.save(confirmRandomKey);
// Get email template
String emailTemplate = getEmailIdTokenTemplate(recipientEmail, randomKey);
helper.setText(emailTemplate, true);
javaMailSender.send(message);
System.out.println("Email Template: " + emailTemplate);
} catch (MessagingException e) {
// 예외 처리 로직 작성
e.printStackTrace(); // 예외 내용을 콘솔에 출력하거나 로깅할 수 있습니다.
// 예외 처리 후 필요한 작업 수행
}
}
ㅇ ^여기서 사용되는 이메일템플릿을 가져오는 getEmailIdTokenTemplate
public String getEmailIdTokenTemplate(String recipientEmail, String randomKey) {
try {
// Create the Thymeleaf context and set variables
Context context = new Context();
context.setVariable("recipientEmail", recipientEmail);
context.setVariable("randomKey", randomKey);
// Process the email template using the template engine
String emailTemplate = templateEngine.process("email_ID_token_template", context);
return emailTemplate;
} catch (Exception e) {
throw new RuntimeException("Failed to process email template.", e);
}
}
ㅇ ^이걸 쓰려면 email_ID_token_template.html 파일을 만들어놔야한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>[KnockKnock] 회원님의 아이디 찾기를 위한 인증번호입니다.</title>
</head>
<body>
<h1>Welcome to KnockKnock</h1>
<p>밑의 키를 복사해서 인증을 완료해주세요.</p>
<p>Secret Key: <span th:text="${randomKey}"></span></p>
</body>
</html>
api/v1/users/me/confirm-id
ㅇ 컨트롤러
/*
회원 아이디 찾기(인증번호 받으면 검증하고 이메일로 아이디 전송)
*/
@PostMapping("/me/confirm-id")
public ResponseEntity<?> meConfirmId(@Valid @RequestBody UserDto.MeEmailConfirm requestBody) throws MessagingException {
try {
boolean isTokenValid = emailService.verifyToken(requestBody.getRandomKey(), requestBody.getEmail());
if (isTokenValid) {
// Send the user ID to the email
emailService.sendEmailWithId(requestBody.getEmail(), "[KnockKnock] 회원 아이디를 보내드립니다.");
return ResponseEntity.ok("이메일을 성공적으로 전송했습니다.");
} else {
return ResponseEntity.badRequest().body(ApiResponse.unAuthorized());
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("인증 처리 중 오류가 발생했습니다.");
}
}
ㅇ DTO
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class MeEmailConfirm {
@NotNull(message = "Null 값은 입력할 수 없습니다.")
@NotBlank
@Email
private String email;
@NotNull
@NotBlank
private String randomKey;
}
ㅇ 서비스
public boolean verifyToken(String token, String email) {
// 이메일 검증 로직 수행
Optional<EmailConfirmRandomKey> emailRandomKeyOptional = emailConfirmRandomKeyRepository.findById(email);
if (emailRandomKeyOptional.isPresent()) {
EmailConfirmRandomKey emailConfirmRandomKey = emailRandomKeyOptional.get();
String randomKey = emailConfirmRandomKey.getRandomKey();
if (randomKey.equals(token)) {
emailConfirmRandomKeyRepository.deleteById(email);
return true; // 인증 성공
}
}
return false; // 인증 실패
}
토큰과 이메일을 파라미터로 받아서 검증.
ㅇ sendEmailWithId
public void sendEmailWithId(String recipientEmail, String subject) throws MessagingException {
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(recipientEmail);
helper.setSubject(subject);
User user = userRepository.findUserByEmail(recipientEmail);
String id = user.getId();
// Get email template
String emailTemplate = getEmailIdTemplate(recipientEmail, id);
helper.setText(emailTemplate, true);
javaMailSender.send(message);
System.out.println("Email Template: " + emailTemplate);
} catch (MessagingException e) {
// 예외 처리 로직 작성
e.printStackTrace(); // 예외 내용을 콘솔에 출력하거나 로깅할 수 있습니다.
// 예외 처리 후 필요한 작업 수행
}
}
ㅇ ^여기서 사용되는 getEmailIdTemplate()
public String getEmailIdTemplate(String recipientEmail, String id) {
try {
// Create the Thymeleaf context and set variables
Context context = new Context();
context.setVariable("recipientEmail", recipientEmail);
context.setVariable("userID", id);
// Process the email template using the template engine
String emailTemplate = templateEngine.process("email_ID_template", context);
return emailTemplate;
} catch (Exception e) {
throw new RuntimeException("Failed to process email template.", e);
}
}
ㅇ ^여기서 사용되는 email_ID_template는
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>[KnockKnock] 회원님의 아이디를 보내드립니다.</title>
</head>
<body>
<h1>Welcome to KnockKnock</h1>
<p>회원님의 아이디는 : <span th:text="${userID}"></span></p>
</body>
</html>
api/v1/users/me/pw
ㅇ 컨트롤러
@PostMapping("/me/pw")
public ResponseEntity<?> mePassword(@Valid @RequestBody UserDto.MePassword requestBody) throws MessagingException {
try {
boolean isIdValid = userService.verifyId(requestBody.getId());
if(isIdValid){
emailService.sendEmailWithTokenPw(requestBody.getEmail(),"[KnockKnock] 회원 비밀번호 찾기 요청 이메일입니다.");
return ResponseEntity.ok("이메일이 성공적으로 발송되었습니다.");
} else{
return ResponseEntity.badRequest().body(ApiResponse.unAuthorized());
}
} catch (MessagingException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("이메일 발송에 실패했습니다.");
}
}
ㅇ DTO
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class MePassword {
@NotNull(message = "Null 값은 입력할 수 없습니다.")
@NotBlank
private String id;
@NotNull(message = "Null 값은 입력할 수 없습니다.")
@NotBlank
@Email
private String email;
}
ㅇ 서비스
public Boolean verifyId(String id){
User existingUser = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다."));
if(existingUser.getId().equals(id)){
return true;
}else {
return false;
}
}
^ id 검증로직
ㅇ 서비스
public void sendEmailWithTokenPw(String recipientEmail, String subject) throws MessagingException {
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(recipientEmail);
helper.setSubject(subject);
String randomKey = generateRandomKey(7);
// EmailConfirmRandomKey 저장
EmailConfirmRandomKey confirmRandomKey = EmailConfirmRandomKey.builder()
.email(recipientEmail)
.randomKey(randomKey)
.build();
emailConfirmRandomKeyRepository.save(confirmRandomKey);
// Get email template
String emailTemplate = getEmailPwTokenTemplate(recipientEmail, randomKey);
helper.setText(emailTemplate, true);
javaMailSender.send(message);
System.out.println("Email Template: " + emailTemplate);
} catch (MessagingException e) {
// 예외 처리 로직 작성
e.printStackTrace(); // 예외 내용을 콘솔에 출력하거나 로깅할 수 있습니다.
// 예외 처리 후 필요한 작업 수행
}
}
ㅇ ^여기서 사용되는 getEmailPwTokenTemplate
public String getEmailPwTokenTemplate(String recipientEmail, String randomKey) {
try {
// Create the Thymeleaf context and set variables
Context context = new Context();
context.setVariable("recipientEmail", recipientEmail);
context.setVariable("randomKey", randomKey);
// Process the email template using the template engine
String emailTemplate = templateEngine.process("email_PW_token_template", context);
return emailTemplate;
} catch (Exception e) {
throw new RuntimeException("Failed to process email template.", e);
}
}
ㅇ ^여기서 사용되는 email_PW_token_template
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>[KnockKnock] 회원님의 비밀번호 찾기를 위한 인증번호입니다.</title>
</head>
<body>
<h1>Welcome to KnockKnock</h1>
<p>밑의 키를 복사해서 인증을 완료해주세요.</p>
<p>Secret Key: <span th:text="${randomKey}"></span></p>
</body>
</html>
api/v1/users/me/confirm-pw
ㅇ 컨트롤러
@PostMapping("/me/confirm-pw")
public ResponseEntity<?> meConfirmPassword(@Valid @RequestBody UserDto.MePasswordConfirm requestBody) throws MessagingException {
try {
boolean isTokenValid = emailService.verifyToken(requestBody.getRandomKey(), requestBody.getEmail());
boolean isIdValid = userService.verifyId(requestBody.getId());
if (isTokenValid && isIdValid) {
// Send the user ID to the email
emailService.sendEmailWithPw(requestBody.getEmail(), "[KnockKnock] 회원 비밀번호를 보내드립니다.");
return ResponseEntity.ok("이메일을 성공적으로 전송했습니다.");
} else{
return ResponseEntity.badRequest().body(ApiResponse.unAuthorized());
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("인증 처리 중 오류가 발생했습니다.");
}
}
토큰검증과 아이디검증을 같이 해서 검증되면 이메일을 보내준다.
ㅇ DTO
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class MePasswordConfirm {
@NotNull(message = "Null 값은 입력할 수 없습니다.")
@NotBlank
private String id;
@NotNull(message = "Null 값은 입력할 수 없습니다.")
@NotBlank
@Email
private String email;
@NotNull(message = "Null 값은 입력할 수 없습니다.")
@NotBlank
private String randomKey;
}
ㅇ 서비스
public void sendEmailWithPw(String recipientEmail, String subject) throws MessagingException {
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(recipientEmail);
helper.setSubject(subject);
User user = userRepository.findUserByEmail(recipientEmail);
String pw = generatePassword(8);
user.setPassword(passwordEncoder.encode(pw));
userRepository.save(user);
// Get email template
String emailTemplate = getEmailPwTemplate(recipientEmail, pw);
helper.setText(emailTemplate, true);
javaMailSender.send(message);
System.out.println("Email Template: " + emailTemplate);
} catch (MessagingException e) {
// 예외 처리 로직 작성
e.printStackTrace(); // 예외 내용을 콘솔에 출력하거나 로깅할 수 있습니다.
// 예외 처리 후 필요한 작업 수행
}
}
임시비밀번호 생성하고 db에 임시비밀번호 인코딩해서 저장하고 이메일템플릿에 담아 보내준다.
ㅇ ^여기서 사용되는 getEmailPwTemplate(), generatePassword()
public String getEmailPwTemplate(String recipientEmail, String password) {
try {
// Create the Thymeleaf context and set variables
Context context = new Context();
context.setVariable("recipientEmail", recipientEmail);
context.setVariable("userPW", password);
// Process the email template using the template engine
String emailTemplate = templateEngine.process("email_PW_template", context);
return emailTemplate;
} catch (Exception e) {
throw new RuntimeException("Failed to process email template.", e);
}
}
public String generatePassword(int keyLength) {
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_+=<>?";
StringBuilder randomKey = new StringBuilder();
SecureRandom secureRandom = new SecureRandom();
for (int i = 0; i < keyLength; i++) {
int randomIndex = secureRandom.nextInt(characters.length());
randomKey.append(characters.charAt(randomIndex));
}
return randomKey.toString();
}
ㅇ ^여기서 사용되는 email_PW_template
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>[KnockKnock] 회원님의 비밀번호를 보내드립니다.</title>
</head>
<body>
<h1>Welcome to KnockKnock</h1>
<p>회원님의 비밀번호는 : <span th:text="${userPW}"></span></p>
</body>
</html>
이미 이메일 보내는 로직들을 만들어놔서 굉장히 편하게 작업을 완료했다!!
이메일은 여기서 끝~
'프로젝트 > 낙낙(KnockKnock)' 카테고리의 다른 글
OAuth2 again!!! (0) | 2023.10.30 |
---|---|
https 를 적용해보자! (0) | 2023.10.11 |
Spring Security JWT 유저, Oauth에 대한 대대적인 공사-4(Oauth2 테스트)(성공) (0) | 2023.09.15 |
이메일인증을 구현해보자(JavaMailSender) (0) | 2023.09.15 |
Spring Security JWT 유저, Oauth에 대한 대대적인 공사-3(Oauth2) (0) | 2023.09.14 |