본문 바로가기

Language/Spring

[Spring Boot] 이메일 인증 코드를 이용한 회원 가입

시나리오

1. 이메일 인증 버튼 클릭
→ 인증 코드를 발급함과 동시에 인증(Auth) 테이블에 이메일과 인증코드를 저장

 

2. 이메일과 인증 코드로 인증 확인 요청

 

3. 인증(Auth) 테이블에 저장된 이메일이 있는 지 확인
→ 없으면 인증 실패

 

4. 인증 테이블에 저장된 이메일과 인증 코드가 맞는 지 확인

맞으면 인증 성공

→ 틀리면 인증 실패

 


Auth Entity

@Entity
@Getter
@NoArgsConstructor
public class Auth {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String email;
    private String authCode;

    public Auth(String email, String authCode){
        this.email = email;
        this.authCode = authCode;
    }

    public void patch(String authCode){
        this.authCode = authCode;
    }
}

MailService 코드 수정

  • 이전 글에서는 true 또는 false를 반환하였으나, 인증 코드를 저장하기 위해 String 인증 코드를 반환
public String sendSimpleMessage(String sendEmail) throws MessagingException {
    String authCode = createCode(); // 랜덤 인증번호 생성

    MimeMessage message = createMail(sendEmail, authCode); // 메일 생성
    try {
        javaMailSender.send(message); // 메일 발송
        return authCode;
    } catch (MailException e) {
        return null;
    }
}

MemberService 코드 수정 및 추가

  • 이전 글에서는 MemberController에서 MailService를 사용하여 발급 처리
  • 하지만, 발급된 인증 코드를 이용한 비즈니스 로직이 필요하기 때문에 MeberService에서 처리하도록 변경
@Service
@RequiredArgsConstructor
public class MemberService {
    private final MemberRepository memberRepository;
    private final MailService mailService;
    private final AuthRepository authRepository;
	
    (생략)

    @Transactional
    public boolean sendAuthcode(String email) throws MessagingException {
        String authCode = mailService.sendSimpleMessage(email);
        if(authCode != null){
            Auth auth = authRepository.findByEmail(email);
            if(auth == null)
                authRepository.save(new Auth(email, authCode));
            else
                auth.patch(authCode);
            return true;
        }
        return false;
    }

    public boolean validationAuthcode(String email, String authCode) {
        Auth auth = authRepository.findByEmail(email);
        if(auth != null && auth.getAuthCode().equals(authCode)){
            authRepository.delete(auth);
            return true;
        }
        return false;
    }
}

sendAuthcode()

  • 인증 코드를 발급하는 메서드
  • Auth 테이블에 입력한 이메일이 존재하는 지 확인
    • 처음 인증 코드를 발급 받는 것이라면 테이블에 저장
    • 발급 받은 적이 있다면 가장 마지막의 인증 코드로만 인증이 될 수 있도록 업데이트 필요
      @Transactional 어노테이션을 사용함으로써 DB에서 가져온 auth를 영속성 컨텍스트에 등록하여 Dirty Check 가능
      즉, auth.patch()로 변화가 일어난 것을 감지하여 메서드 종료 시 자동으로 update query문이 발생 됨

 

validationAuthcode()

  • 이메일과 인증 코드를 검증하는 메서드
  • 사용자가 입력한 Email과 인증 코드가 완전히 일치하는 지 확인
    → 일치하다면 인증 테이블에서 삭제하고 인증 성공 처리
  • 그 외 (입력한 이메일이 인증 코드를 발급 받지 않았거나, 인증 코드가 일치하지 않는 경우)
    → 인증 실패 처리

MemberController

@RestController
@RequestMapping("/api/member")
@RequiredArgsConstructor
@Slf4j(topic = "MemberController")
public class MemberController {
    private final MemberService memberService;

    (생략)

    @GetMapping("/email/auth/{email}")
    public ResponseEntity<String> requestAuthcode(@PathVariable String email) throws MessagingException {
        boolean isSend = memberService.sendAuthcode(email);
        return isSend ? ResponseEntity.status(HttpStatus.OK).body("인증 코드가 전송되었습니다.") :
                ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("인증 코드 전송이 실패하였습니다.");
    }

    @PostMapping("/email/auth")
    public ResponseEntity<String> validateAuthcode(@RequestParam(name = "email")String email,
                                                  @RequestParam(name = "auth")String authCode) {
        boolean isSuccess = memberService.validationAuthcode(email, authCode);
        return isSuccess ? ResponseEntity.status(HttpStatus.OK).body("이메일 인증에 성공하였습니다.") :
                ResponseEntity.status(HttpStatus.BAD_REQUEST).body("이메일 인증에 실패하였습니다.");
    }
}

 


Postman을 이용한 테스트

  • 인증코드 발급 1회 시도

  • 인증코드 발급 2회 시도 + 영속성 컨텍스트 Dirty Check

  • 인증 코드 확인