회원 가입 인증 메일 확인

Yuri Lee·2020년 11월 6일
0

인증 메일 확인하는 이유?

실제 계정 메일이 아니면 실제 유저 수를 확인할 수 없다. 알림 이메일 전송 마찬가지이다. 의사소통불가의 문제

Get "/check-email-token" token=tokenemail={token} email={email}

  • 이메일이 정확하지 않은 경우에 대한 에러 처리
  • 토큰이 정확하지 않은 경우에 대한 에러 처리
  • 이메일과 토큰이 정확한 경우 가입 완료 처리
    • 가입 일시 설정
    • 이메일 인증 여부 true로 설정

인증 확인 뷰

  • 입력값에 오류가 있는 경우 적절한 메시지 출력.
  • 인증이 완료된 경우, 환영 문구와 함께 몇번째 사용자인지 보여줄 것.

AccountController

가장 먼저 무엇을 해볼 것이라면 이메일에 해당하는 유저가 있는지 확인해 볼 것이다. 지난번에 만들어놓은 레파지토리 활용한다. 일단 여기서는 repository 를 도메인 계층을 보려고 한다. 다른 패키지이긴 하지만 Account와 같은 레벨로 볼 것이다. Account를 참조하듯이 AccountRepository도 여러곳에서 참조해도 괜찮다고 볼 것! 😗😗 대신에 service 와 controller 를 Reepository 나 domain Entity에서 참조하진 않을 것이다.

AccountController.java

    @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/checkedEmail";
    	}

    	// 토큰이 정확하지 않은 경웨 대한 에러 처리
    	if (!account.getEmailCheckToken().equals(token)) {
    		model.addAttribute("error", "wrong token");
    		return "account/checkedEmail";
    	}
    	
    	//이메일과 토큰이 정확한 경우 가입 완료 처리
    	account.setEmailVerified(true);
    	account.setJoinedAt(LocalDateTime.now());
    	model.addAttribute("numberOfUser", accountRepository.count()); // 몇번째 유저이냐
    	model.addAttribute("nickname", account.getNickname()); // 이름 표출
    	return "account/checkedEmail";
    }
  • accountRepository.count() : 레파지토리에 기본으로 제공되는 count()라는 함수가 있음.
    @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.isValidToken(token)) {
            model.addAttribute("error", "wrong.token");
            return view;
        }

        account.completeSignUp();
        accountService.login(account);
        model.addAttribute("numberOfUser", accountRepository.count());
        model.addAttribute("nickname", account.getNickname());
        return view;
    }

Checked-email.html 뷰

  • 인증메일확인 뷰 html 파일을 추가한다.

Error!!!

  • 에러 발생 ? (null point exception) 어디서 널값이 발생했을까..? 이럴때는 당황하지 말고 디버거를 찍어보자!
    public void processNewAccount(SignUpForm signUpForm) {
            Account newAccount = saveNewAccount(signUpForm);
            newAccount.generateEmailCheckToken();
            sendSignUpConfirmEmail(newAccount);

    }

newAccount는 현재 detach 객체라서 db에 싱크가 되지 않은 상태이다. 따라서 emailChecktoken값이 db에 저장되지 않았다. 생성했는데 null 인 이유는?..

    private Account saveNewAccount(SignUpForm signUpForm) {
        Account account = Account.builder()
                .email(signUpForm.getEmail())
                .nickname(signUpForm.getNickname())
                .password(passwordEncoder.encode(signUpForm.getPassword()))
                .meetCreatedByWeb(true)
                .meetEnrollmentResultByWeb(true)
                .meetUpdatedByWeb(true)
                .build();
        Account newAccount = accountRepository.save(account);
        return newAccount;
    }

saveNewAccount 여기서 이미 저장을 했는데 저장을 하면 return한 객체는 accountRepository.save(account); 이 안에서만 트랜잭션이 일어난다. 즉 save 메서드 안에서만 트랜잭션 처리가 되었기 때문에 이 안에서는 해당하는 엔티티가 jpa persistence 상태이다. 나온 다음에!

    public void processNewAccount(SignUpForm signUpForm) {
            Account newAccount = saveNewAccount(signUpForm);
            newAccount.generateEmailCheckToken();
            sendSignUpConfirmEmail(newAccount);

    }

여기서는 detached 상태이다. 왜? 트랜잭션 범위를 벗어났으니까! 그래서 트랜젝션 어노테이션을 붙여줘야 한다. 매우매우매우 중요! 그래서 saveNewAccount 안에서 save가 호출되더라도 나와서도 계속해서 트랜잭션 범위 안에 있기 때문에 detached 상태가 아니라 persistence 상태가 유지된다. persistence 상태의 객체는 트랜잭션이 종료될 때 상태를 db에 싱크하게 된다. 여기서 생성한 newAccount.generateEmailCheckToken() 토큰값이 이때는 저장될 것이다.

TEST 해보기

AccountControllerTest.java

  @DisplayName("회원 가입 처리 - 입력값 정상")
    @Test
    void signUpSubmit_with_correct_input() throws Exception {
        mockMvc.perform(post("/sign-up")
                .param("nickname", "keesun")
                .param("email", "keesun@email.com")
                .param("password", "12345678")
                .with(csrf()))
                .andExpect(status().is3xxRedirection())
                .andExpect(view().name("redirect:/"))
                .andExpect(authenticated().withUsername("keesun"));

        // 이메일이 있는지 확인
        Account account = accountRepository.findByEmail("keesun@email.com");
        assertNotNull(account);
        assertNotEquals(account.getPassword(), "12345678"); //즉 평문 그대로 저장 안한다는 것이 입증됨
        assertNotNull(account.getEmailCheckToken()); // 토큰이 null 이 아닌지 확인하기 
        // 아무런 타입 SimpleMailMessage 인스턴스를 가지고 sender가 호출되었는가만 확인 -> 메일을 보냈는지 확인하는 것
        then(javaMailSender).should().send(any(SimpleMailMessage.class));
    }

assertNotNull(account.getEmailCheckToken()); // 토큰이 null 이 아닌지 확인하기


출처 : 인프런 백기선님의 스프링과 JPA 기반 웹 애플리케이션 개발

profile
Step by step goes a long way ✨

0개의 댓글