본문 바로가기
프로젝트/낙낙(KnockKnock)

이메일인증을 구현해보자(JavaMailSender)-2(회원아이디,패스워드찾기 )(성공)

by 티코딩 2023. 9. 19.

개발자 테스트 체크리스트를 만들다가 전 프로젝트랑 비교했을 때, 내가 회원 아이디 찾기, 비밀번호 찾기를 구현하지 않았다는걸 알게 되었다. 그래서 부랴부랴 만들었다. 이메일인증을 이미 구현했기 때문에, 이메일을 이용한 방법을 사용해보기로 했다.

 

ㅇ 간단한 프로세스(아이디 찾기)

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>

 

 

이미 이메일 보내는 로직들을 만들어놔서 굉장히 편하게 작업을 완료했다!!

이메일은 여기서 끝~