팀 프로젝트를 진행하면서, 프로젝트의 매력 기능 중 하나로 이메일 인증을 구현하기로 했다.
아이디어를 제시하긴 했지만, 다들 기능의 적용을 흔쾌히 수락해주어 열심히 구현해보았다.
실제 이메일을 보내고 인증과정을 거치니 재미있는 것 같다.
눈에 보이지 않는 데이터를 다루다가, 직접 이메일로 데이터를 주고 받아 인증하니 백엔드 입장에서 재미를 느낄만 한 것 같다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-mail'
이메일 인증 구현을 위한 redis와 mail 디펜던시를 추가해주었다.
이를 통해, 해당 프로젝트에서 mail 기능와 redis 를 사용할 수 있게 된다.
spring:
mail:
host: smtp.gmail.com
port: 587
username: {email}
password: {password}
properties:
mail:
smtp:
auth: true
starttls:
enable: true
이메일을 보내기 위한 기본적인 세팅을 해준다. gmail 이메일과 password 입력을 yml 파일에 입력해 값을 지정해준다.
@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);
}
}
@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을 던지고, 일치한다면 해당 이메일은 회원가입을 진행할 수 있게 해야되기 때문에 하루동안 화이트리스트로 등록하는 로직이다.
다른 글들을 참고하며 기능을 구현했지만, 내 생각도 많이 들어간 코드라고 생각한다.
이메일 인증을 하지 않으면 회원가입을 거절하는 WhiteList 기능이라든가,
white list 기능 구현을 위한 key 값 prefix 설정 등 이것 저것 고민하니 좋은 결과가 나온 것 같다.
redis 에 list 형태나 set 형태로 이메일 데이터를 저장할 경우, time-to-live 설정 방법을 찾지 못해 EMAIL:VERIFIED:{email}
+ true
의 키밸류 쌍으로 화이트리스트 검증 로직을 거치고 있어서 살짝 아쉽긴 하다.
참조하여 코드를 쓰더라도, 나의 생각을 담아내자.