스프링과 JPA 기반 웹 애플리케이션 개발 #14 회원 가입 인증 메일 확인

Jake Seo·2021년 5월 27일
0

스프링과 JPA 기반 웹 애플리케이션 개발 #14 회원 가입 인증 메일 확인

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

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

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


회원 가입: 인증 메일 확인

  • GET /check-email-token token=${token}&email=${email} 요청 처리
    • 이메일이 정확하지 않은 경우에 대한 에러 처리
      • 실제 존재하는 사람인지, 악성 유저가 아닌지에 대한 확인
        • 소셜 인증을 받으면 이러한 문제를 조금 해결할 수 있음
    • 토큰이 정확하지 않은 경우에 대한 에러 처리
    • 이메일과 토큰이 정확한 경우 가입 완료 처리
      • 가입 일시 설정
      • 이메일 인증 여부 true로 설정
  • 인증 확인 뷰
    • 입력 값에 오류가 있는 경우, 적절한 메세지 출력
      • 구체적으로 알려주진 않고, 정확하지 않다는 정보만 모호하게 알려줄 필요가 있다.
        • 잘못하면 공격자에게 많은 정보를 줄 수도 있다.
    • 인증이 완료된 경우 환영 문구와 함께 몇번째 사용자인지 보여줄 것

AccountController.class

   @GetMapping("/check-email-token")
    public String checkEmailToken(String token, String email, Model model) {
        // 리포지토리를 도메인과 같은 레벨로 봄.
        // 대신 서비스나 컨트롤러를 리포지토리나 도메인 엔티티에서 참조하진 않을 것 (설계 위배)
        Account account = accountRepository.findByEmail(email);

        if(account == null) {
            model.addAttribute("error", "wrong.email");
            return "account/checked-email";
        }

        if(!account.getEmailCheckToken().equals(token)) {
            model.addAttribute("error", "wrong.token");
            return "account/checked-email";
        }

        account.setEmailVerified(true);
        account.setJoinedAt(LocalDateTime.now());
        model.addAttribute("nickname", account.getNickname());
        model.addAttribute("numberOfUser", accountRepository.count());

        return "account/checked-email";
    }

이메일 인증 페이지인데, 크게 특별한 건 없다. 계정을 못찾거나 계정에 있는 토큰 값이 일치하지 않으면 에러 객체를 내보낸다. 그리고, 에러가 아니라면 emailVerifiedtrue로 변경해주고, 현재 시각을 가입 시각으로 한다.

accountRepository.count()는 Spring Data JPA에서 기본적으로 제공하는 메소드인데, 데이터가 몇 개 있는지에 대한 정보를 반환한다.

AccountService.class

    @Transactional
    // 트랜잭션 문제를 주의하고, `Repository` 에서는 `(readOnly = true)`를 사용하고,
    // 실제 JPA 객체를 다루는 곳에는 `@Transactional`을 붙여주자.
    // @Transactional 이 없으면 `AccountRepository`에서 데이터를 가져올 수는 있으나, detached 상태여서 해당 데이터를 수정하거나 할 수 없다.
    public void processNewAccount(SignUpForm signUpForm) {
        Account newAccount = saveNewAccount(signUpForm);
        sendSignUpConfirmEmail(newAccount);

여기서는 굉장히 큰 버그가 발생할 수 있는 곳이었다. accountRepository.save() 메소드가 있는 saveNewAccount() 메소드에서는 @Transactional이 적용되었었지만, processNewAccount() 메소드에서는 @Transactional이 적용되지 않았었다. @Transactional이 적용되지 않은 상태에서 JPA 객체는 detached 상태이다. 그래서 더티체크가 진행되지 않고 변경사항도 DB에 반영되지 않고, 자바 메모리단에서만 반영된다.

자바 메모리단에서 반영된 객체를 자바 메모리에서 삭제된 이후에 다시 해당 객체를 로드하면, 해당 반영사항은 지워져있다. JPA를 사용할 때는 이러한 현상을 조심해야 한다.

checked-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>
  <!-- py-5는 y축 패딩 5px를 말한다. (top, bottom) -->
  <div class="py-5 text-center" th:if="${error}">
    <p class="lead">스터디올래 이메일 확인</p>
    <div class="alert alert-danger" role="alert">
      이메일 확인 링크가 정확하지 않습니다.
    </div>
  </div>
  <div class="py-5 text-center" th:if="${error == null}">
    <p class="lead">스터디올래 이메일 확인</p>
    <h2>
      이메일을 확인했습니다. <span th:text="${numberOfUser}">x</span>번째 회원,
      <span th:text="${nickname}">xxx</span>님 환영합니다.
    </h2>
    <small class="text-info">이제부터 가입할 때 사용할 이메일 또는 닉네임과 패스워드로 로그인할 수 있습니다.</small>
  </div>
  <script th:replace="fragments :: form-validation"></script>
</body>
</html>

이건 뭐 딱히 별거 없다. th:text를 이용해서 span에 데이터를 넣어주었다.

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

0개의 댓글