TIL - #32 Redis 이용 이메일 인증 구현

Quann·2023년 2월 12일
0

00. 개요

팀 프로젝트를 진행하면서, 프로젝트의 매력 기능 중 하나로 이메일 인증을 구현하기로 했다.

아이디어를 제시하긴 했지만, 다들 기능의 적용을 흔쾌히 수락해주어 열심히 구현해보았다.

실제 이메일을 보내고 인증과정을 거치니 재미있는 것 같다.

눈에 보이지 않는 데이터를 다루다가, 직접 이메일로 데이터를 주고 받아 인증하니 백엔드 입장에서 재미를 느낄만 한 것 같다.


01. 구현

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-mail'

이메일 인증 구현을 위한 redis와 mail 디펜던시를 추가해주었다.
이를 통해, 해당 프로젝트에서 mail 기능와 redis 를 사용할 수 있게 된다.

application.yml

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: {email}
    password: {password}

    properties:
      mail:
        smtp:
          auth: true

          starttls:
            enable: true

이메일을 보내기 위한 기본적인 세팅을 해준다. gmail 이메일과 password 입력을 yml 파일에 입력해 값을 지정해준다.

RedisUtil.java

@RequiredArgsConstructor
@Service
public class RedisUtil {

  private final RedisTemplate<String, Object> redisTemplate;
  private final StringRedisTemplate stringRedisTemplate;

  public String getData(String key) {
    ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
    return valueOperations.get(key);
  }

  public void setData(String key, String value) {
    ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
    valueOperations.set(key, value);
  }

  public void setDataExpire(String key, String value, long duration) {
    ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
    Duration expireDuration = Duration.ofSeconds(duration);
    valueOperations.set(key, value, expireDuration);
  }

}

EmailService.java

@Service
@RequiredArgsConstructor
public class EmailService {

  private final JavaMailSender emailSender;
  private final RedisUtil redisUtil;

  public static final String VERIFY_KEY_PREFIX = "EMAIL:VERIFY:"; // EMAIL:VERIFY:email - A3GA1E
  public static final String WHITELIST_KEY_PREFIX = "EMAIL:VERIFIED:"; // EMAIL:VERIFIED - email, email ...

  public void sendMail(
      final String to,
      final String sub,
      final String text
  ) {
    MimeMessage message = emailSender.createMimeMessage();

    try {
      message.addRecipient(RecipientType.TO, new InternetAddress(to));
      message.setSubject(sub, "utf-8");
      message.setText(text, "utf-8", "html");
    } catch (MessagingException e) {
      throw new IllegalArgumentException("이메일 전송 실패");
    }
    emailSender.send(message);
  }

  @Transactional
  public void makeVerificationAndSendMail(final String email) {
    if (email == null) {
      throw new AuthException(INVALID_EMAIL_OR_PW);
    }
    String code = UUID.randomUUID().toString().substring(0, 7).toUpperCase();
    redisUtil.setDataExpire(VERIFY_KEY_PREFIX + email, code, 60 * 3L); // 3분 인증 시간
    sendMail(email, EmailMessage.MESSAGE_TITLE, EmailMessage.createMessage(code));
  }

  @Transactional
  public void verifyEmail(final String email, final String code) {
    String validCode = redisUtil.getData(VERIFY_KEY_PREFIX + email);
    if (!code.equals(validCode)) {
      throw new AuthException(INVALID_CODE);
    }
    redisUtil.setDataExpire(WHITELIST_KEY_PREFIX + email, "true",
        60 * 60 * 24L); // 1일 이메일 화이트리스트 유지
    redisUtil.deleteData(VERIFY_KEY_PREFIX + email);
  }

}

코드에서 봐도 알 수 있듯이,
sendEmail() 은 메세지를 전송하는 기능을 담당하는 메서드이며,

makeVerificationAndSendMail() 메서드는 사용자에게 전달할 UUID 기반의 7자리 코드를 만들어 내고, redis에 EMAIL:VERIFY:{email} 이라는 키와 코드의 밸류 쌍으로 3분의 인증시간을 두어 보관한다.
3분안에 인증을 진행하지 못할 경우, 해당 데이터는 삭제되어 이메일 인증을 진행할 수 없게 하기 위한 로직이다.

verifyEmail() 메서드의 경우, email과 code 값을 받아 EMAIL:VERIFY:{email} 키 값에 어떤 밸류가 있는지 찾아보고, 넘어온 코드값과 비교한다. 일치하지 않으면 Exception을 던지고, 일치한다면 해당 이메일은 회원가입을 진행할 수 있게 해야되기 때문에 하루동안 화이트리스트로 등록하는 로직이다.


02. 결론

다른 글들을 참고하며 기능을 구현했지만, 내 생각도 많이 들어간 코드라고 생각한다.

이메일 인증을 하지 않으면 회원가입을 거절하는 WhiteList 기능이라든가,
white list 기능 구현을 위한 key 값 prefix 설정 등 이것 저것 고민하니 좋은 결과가 나온 것 같다.

redis 에 list 형태나 set 형태로 이메일 데이터를 저장할 경우, time-to-live 설정 방법을 찾지 못해 EMAIL:VERIFIED:{email} + true 의 키밸류 쌍으로 화이트리스트 검증 로직을 거치고 있어서 살짝 아쉽긴 하다.


03. 오늘의 한 문단

참조하여 코드를 쓰더라도, 나의 생각을 담아내자.

profile
코드 중심보다는 느낀점과 생각, 흐름, 가치관을 중심으로 업로드합니다!

0개의 댓글