출처
www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1/dashboard
github.com/devjun63/whiteship-studyolle/commit/0480b970573e43c9a144008c7ed4f85c3195e120
GET "/check-email-token" token=${token} email=${email} 요청 처리
- 이메일이 정확하지 않은 경우에 대한 에러 처리
- 토큰이 정확하지 않은 경우에 대한 에러 처리
- 이메일과 토큰이 정확한 경우 가입 완료 처리
- 가입 일시 설정
- 이메일 인증 여부 true로 설정
인증 확인 뷰
- 입력값에 오류가 있는 경우 적절한 메시지 출력
- 인증이 완료된 경우, 환영 문구와 함께 몇 번째 사용자인지 보여줄 것
이메일 인증을 하는 이유
이메일 인증을 하지 않는 경우 무작위 엉터리 이메일로 가입하는 경우가 많아진다.
실제 이메일 계정이 아닌 무작위 계정이 늘어나기에 실제 유저를 확보하기도 어렵다.
알림 메세지같은 경우 제대로 된 메시지를 보낼 수 없다.
서비스에서 의사소통에 문제가 생긴다. -> 서비스 장애
소셜 인증 적용 -> 커버가 된다. (facebook, kakaotalk, google등)
자체 이메일 검증 기능 보유
뷰에서 에러의 이유를 구체적으로 설명하지 않아도 된다.
보안과 관련된 응답 정보는 모호하게 제공하라 ( 해커의 대조시도 등 방지)
AccountController에 get방식으로 checkEmailToken Method
@GetMapping("/check-email-token")
public String checkEmailToken(String token, String email, Model model) {
Account account = accountRepository.findByEmail(email);
String view = "account/checked-email";
if (account == null){
model.addAttribute("error","wrong email");
return view;
}
if(!account.getEmailCheckToken().equals(token)){
model.addAttribute("error","wrong email");
return view;
}
account.setEmailVerified(true);
account.setJoinedAt(LocalDateTime.now());
model.addAttribute("numberOfUser", accountRepository.count());
model.addAttribute("nickname", account.getNickname());
return view;
}
템플릿 checked-email.html 생성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"><!--thymeleaf 사용하기 위한 xmlnamespace 설정-->
<head>
<meta charset="UTF-8">
<title>StudyOlle</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<!--style은 head에 위치시켜 rendering 적용시켜 렌더링 될 때 스타일 적용시켜 읽히고-->
<style>
.container {
max-width: 100%:
}
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<a class="navbar-brand" href="/" th:href="@{/}"> <!--thymeleaf로 렌더링 할때 href값을 이 값을 쓴다.-->
<img src="/images/logo_sm.png" width="30" height="30">
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<form th:action="@{/search/study}" class="form-inline" method="get">
<input class="form-control mr-sm-2" name="keyword" type="search" placeholder="스터디 찾기">
</form>
</li>
</ul>
<ul class="navbar-nav justify-content-end">
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{/login}">로그인</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" th:href="@{/signup}">가입</a>
</li>
</ul>
</div>
</nav>
<div class="py-5 text-center" th:if="${error}">
<p class="lead">스터디 올래 이메일 확인</p><!-- lead는 일반 글자체 보다 얅고 크다-->
<div class="alert alert-danger" role="alert">
이메일 확인 링크가 정확하지 않습니다.
</div>
</div>
<div class="py-5 text-center" th:if="${error == null}"><!--p ->padding y -> top bottom py-5 (5el) -->
<p class="lead">스터디 올래 이메일 확인</p>
<h2>
이메일을 확인 했습니다. <span th:text="${numberOfUser}">10</span>번째 회원,
<span th:text="${nickname}">위스키</span>님 가입을 축하합니다.
</h2>
<small class="text-info">이제부터 가입할 때 사용한 이메일 또는 닉네임과 패스워드로 로그인 할 수 있습니다.</small>
</div>
</body>
</html>
가입 후 나온 토큰과 이메일 값
AccountService의 이 부분이 문제다.
결과부터 말하자면 Transaction이 없어서 생성한 토큰이 DB에 저장되지 않았다.
public void processNewAccount(SignUpForm signUpForm) {
Account newAccount = saveNewAccount(signUpForm); // detached (분리된)상태
newAccount.generateEmailCheckToken(); //UUID
sendSignUpConfirmEmail(newAccount);
}
private Account saveNewAccount(@Valid SignUpForm signUpForm) {
Account account = Account.builder()
.email(signUpForm.getEmail())
.nickname(signUpForm.getNickname())
.password(passwordEncoder.encode(signUpForm.getPassword()))
.studyCreateByWeb(true)
.studyEnrollmentResultByWeb(true)
.studyUpdateByWeb(true)
.build();
return accountRepository.save(account);
// jpa entity lifecycle persist 상태
}
->
@Transactional
public void processNewAccount(SignUpForm signUpForm) {
Account newAccount = saveNewAccount(signUpForm); //persist 유지
newAccount.generateEmailCheckToken(); //UUID
sendSignUpConfirmEmail(newAccount);
-> transaction상태 해제되면서 DB에 싱크함
}
private Account saveNewAccount(@Valid SignUpForm signUpForm) {
Account account = Account.builder()
.email(signUpForm.getEmail())
.nickname(signUpForm.getNickname())
.password(passwordEncoder.encode(signUpForm.getPassword()))
.studyCreateByWeb(true)
.studyEnrollmentResultByWeb(true)
.studyUpdateByWeb(true)
.build();
return accountRepository.save(account);
// jpa entity lifecycle persist 상태
}
saveNewAccount 메서드의 accountRepsository.save는 persist상태이지만
processNewAccount에서 리턴된 상태의 newAccount는 detached(분리)된 상태여서
DB에 싱크가 제대로 되지 않는다.
@Transactional 어노테이션을 붙이면 persist상태가 유지된다.
persist 상태의 객체는 transaction상태가 종료 될때 DB에 sync하게 된다.
JPA 엔티티 생명주기 및 영속성 컨텍스트 참고 자료
'프로젝트 정리 > 스프링과 JPA 기반 웹 애플리케이션 개발' 카테고리의 다른 글
12. 회원 가입: 가입 완료 후 자동 로그인 (0) | 2021.02.25 |
---|---|
11. 회원 가입: 인증 메일 확인 테스트 및 리팩토링 (0) | 2021.02.25 |
9. 회원 가입: 패스워드 인코더 (0) | 2021.02.24 |
7. 회원 가입 : 리팩토링 및 테스트 (0) | 2021.02.19 |
6 - 2 . 회원 가입 폼 서브밋 처리 (0) | 2021.02.17 |