스프링과 JPA 기반 웹 애플리케이션 개발 #22 가입 확인 이메일 재전송

Jake Seo·2021년 5월 30일
0

스프링과 JPA 기반 웹 애플리케이션 개발 #22 가입 확인 이메일 재전송

해당 내용은 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발의 강의 내용을 바탕으로 작성된 내용입니다.

강의를 학습하며 요약한 내용을 출처를 표기하고 블로깅 또는 문서로 공개하는 것을 허용합니다 라는 원칙 하에 요약 내용을 공개합니다. 출처는 위에 언급되어있듯, 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발입니다.

제가 학습한 소스코드는 https://github.com/n00nietzsche/jakestudy_webapp 에 지속적으로 업로드 됩니다. 매 커밋 메세지에 강의의 어디 부분까지 진행됐는지 기록해놓겠습니다.


가입 확인 이메일 재전송 기능

  • 요구사항
    • 가입 확인 이메일을 재전송할 수 있는 기능 제공
    • 너무 자주 전송할 경우 리소스를 낭비할 수 있으니, 1시간에 1번만 가능하게
  • GET /check-email
    • 가입 확인 이메일을 전송한 이메일 주소(가입할 때 입력한 이메일 주소)를 화면에 보여줌
    • 재전송 버튼을 보여주고 클릭하면 GET /resend-confirm-email 요청을 전송한다.
  • GET /resend-confirm-email
    • 인증 메일을 다시 전송할 수 있는지 확인
      • 보낼 수 있으면 전송하고 첫 페이지 리다이렉트
      • 보낼 수 없으면 에러 메세지를 모델에 담아주고 이메일 확인 페이지 다시 보여주기

AccountController 코드 수정

    @GetMapping("/check-email")
    public String checkEmail(@CurrentUser Account account, Model model) {
        model.addAttribute(account);

        if (account.isEmailVerified()) {
            return "redirect:/";
        }

        return "account/check-email";
    }

    @GetMapping("/resend-confirm-email")
    public String resendEmail(@CurrentUser Account account, Model model) {
        model.addAttribute(account);

        if (account.getResendEmailAt() != null &&
                !account.canSendEmailAgain()) {
            long minutes = LocalDateTime.now().until(account.getResendEmailAt().plusHours(1), ChronoUnit.MINUTES);

            model.addAttribute("error", true);
            model.addAttribute("message",
                    "이메일 재전송에 실패하였습니다. 이메일을 재전송한지 1시간 이내입니다. " + minutes + "분 후에 다시 보낼 수 있습니다.");

            return "account/check-email";
        }

        accountService.sendAccountConfirmEmail(account);
        model.addAttribute("error", false);
        model.addAttribute("message", "이메일 재전송에 성공하였습니다.");

        // 사실 Redirect를 시키는 것이 좋다.
        // 왜냐하면 새로고침 시에 계속 재전송이 발생할 수 있기 때문에.
        // 그런데 이 경우에는 어차피 1시간 검증 로직을 넣어서 상관은 없다.
        return "account/check-email";
    }

/resend-confirm-email 경로에 대한 처리를 추가했다. GET 메소드로 호출 시 이메일을 다시 보내주며, 이메일을 다시 보내는데 성공했는지 결과를 나타내준다.

1시간 이내에 다시한번 호출되면 트래픽이나 메일서비스 오남용 등의 이유로 이메일이 재전송되지 않는다.

사실 이렇게 요청에 의한 처리를 구성할 때는 뷰를 반환해주지 않는 것이 더 옳다. 뷰를 반환해주지 않고 redirect:/로 처리해야 새로고침 시 Form을 재전송하는 버그를 막는데, 여기에는 1시간 제약조건이 있어서 그냥 뷰를 반환하는 방식으로 작성하였다.

실패하면 위와 같이 실패했다는 내용이 뜨며, 몇 분 후에 재전송 할 수 있는지 뜬다.

AccountService 코드 수정

    public void sendAccountConfirmEmail(Account account) {
        // 이메일을 다시 보낸 경우
        if (account.getEmailCheckToken() != null) {
            account.setResendEmailAt(LocalDateTime.now());
        }

이메일을 이미 보내서, 이메일 체크 토큰을 가지고 있는 Account 객체에 대해, 다시 한번 이메일을 보내는 로직을 수행하면, ResendEmailAt이라는 멤버에 메일을 다시 보낸 시각을 기록하도록 했다. 이 시각을 기점으로 1시간 내에는 다시 한번 메일을 보낼 수 없다.

Account 코드 수정

    public boolean canSendEmailAgain() {
        return this.resendEmailAt.isBefore(LocalDateTime.now().minusHours(1));
    }

canSendEmailAgain()이라는 메소드를 구성하여, 현재 시간으로부터 1시간이 지났다면 true 아니라면 false를 리턴하여, 이메일을 보낼 수 있는 적절한 시간이 되었는지 판단하는 로직이다.

check-email.html 코드 수정

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>이메일 확인</title>
    <th:block th:replace="fragments :: headLibraryInjection"></th:block>
</head>
<body class="bg-light">
<th:block th:replace="fragments :: main-nav"></th:block>
<div class="alert alert-info" role="alert" th:if="${error != null && !error}" th:text="${message}">
    이메일을 재전송하였습니다.
</div>
<div class="alert alert-danger" role="alert" th:if="${error != null && error}" th:text="${message}">
    이메일을 재전송에 실패하였습니다.
</div>

<!-- py-5는 y축 패딩 5px를 말한다. (top, bottom) -->
<div class="py-5 text-center">
    <p class="lead">스터디올레 가입</p>
    <h2>
        스터디올레 서비스를 사용하려면 인증 이메일을 확인하세요.
    </h2>
    <p th:text="${account.getEmail()}"></p>
    <a class="btn btn-outline-primary" href="#" th:href="@{/resend-confirm-email}">인증 이메일 다시 보내기</a>
</div>
<script th:replace="fragments :: form-validation"></script>
</body>
</html>

이건 뭐 별로 특별한 건 없다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글