유학생들을 위한 커뮤니티 앱 "밍글"을 개발하며 학교 인증을 고민하던 중 학교 이메일로 재학생을 인증하기로 결심했습니다.
이에 회원가입 과정에서 학교 이메일로 인증코드를 보내 인증하는 방식을 Spring Boot + SMTP(Google Workspace) + ElastiCache(Redis)로 구현한 과정을 적어보고자 합니다.
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
이메일 발송을 위해서 spring-boot-starter-mail dependency 추가
인증번호 유효시간 동안 Redis에 저장하기 위한 spring-boot-starter-data-redis dependency 추가
application.yml 설정
spring:
mail:
// host, port는 google에서 정해준 값
host: smtp-relay.gmail.com //밍글 도메인에서 보내기에 relay 필요
port: 587 //for TLS
// Gmail 계정 정보
username: 이메일@domain.com //(밍글 google workspace 도메인)
password: ******* // 1
properties:
mail.smtp.auth: true
mail.smtp.starttls.enable: true
// ElastiCache(Redis) 설정
redis:
host: elastiCache 클러스터 주소
port: 6379
주석 1: 원래라면 해당 이메일 계정의 비밀번호를 적어야 했지만, 구글이 2022년 5월 30일부터 이를 막아 2단계 인증을 통해 발급받은 앱 비밀번호를 넣어줘야 합니다.
아래 SMTP 설정에서 앱 비밀번호에 대해 더 자세히 설명하겠습니다.
앱 비밀번호를 발급하는 순서는 다음과 같습니다.
Google Workspace의 SMTP 릴레이는 관리 콘솔에서 활성화해야 동작하기에 관련 설정을 해보겠습니다.
2 . SMTP Relay Service에 아래의 설정 옵션 표를 참고해 다음과 같이 규칙을 추가해주었습니다.
이렇게 Google Workspace와 SMTP 설정을 마쳤습니다.
이제 실제 인증번호 전송 코드를 살펴보겠습니다.
이메일 인증코드 전송 API를 작성해보겠습니다.
@PostMapping("sendcode") // 이메일 인증코드 전송 API
public BaseResponse<String> sendCode(@RequestBody PostEmailRequest req) {
try {
if (req.getEmail().isEmpty()) {
return new BaseResponse<>(EMAIL_EMPTY_ERROR);
}
if (!isRegexEmail(req.getEmail())) { //이메일 형식(정규식) 검증
return new BaseResponse<>(EMAIL_FORMAT_ERROR);
}
authService.sendCode(req.getEmail());
return new BaseResponse<>("인증번호가 전송되었습니다.");
} catch (BaseException e) {
return new BaseResponse<>(e.getStatus());
}
}
이메일 정규식 검증을 해주고 문제가 없다면 이메일을 전달하며 서비스 단을 호출합니다.
@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class AuthService {
private final JavaMailSender javaMailSender; //MIME을 지원하는 MailSender 구현체
private final RedisUtil redisUtil;
@Value("${spring.mail.username}")
private String from;
// 1.4.1인증번호 생성
@Transactional
public void sendCode(String email) throws BaseException {
Random random = new Random();
String authKey = String.valueOf(random.nextInt(888888) + 111111);
sendAuthEmail(email, authKey);
}
// 1.4.2인증번호 이메일 전송
private void sendAuthEmail(String email, String authKey) throws BaseException{
String subject = "Mingle의 이메일 인증번호를 확인하세요";
String text = "\n\n인증번호는 " + authKey + " 입니다.";
try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8");
helper.setFrom(from); //yml에서 @Value로 가져온 보내는 송신자 이메일 주소
helper.setTo(email); //인자로 받은 이메일 수신자 주소
helper.setSubject(subject); //제목
//helper.setText(text, true); //템플릿 사용으로 주석처리
//템플릿에 전달할 데이터 설정
Context context = new Context();
context.setVariable("authKey", authKey); //Template에 전달할 데이터(authKey) 설정
//메일 내용 설정 : 템플릿 프로세스
String html = springTemplateEngine.process("index", context);
helper.setText(html, true);
helper.addInline("image", new ClassPathResource("templates/images/image-1.jpeg"));
javaMailSender.send(mimeMessage);
} catch(MessagingException e) {
throw new BaseException(EMAIL_SEND_FAIL);
}
//Redis에 3분동안 인증코드 {email, authKey} 저장
try {
redisUtil.setDataExpire(email, authKey, 60 * 3L);
} catch (Exception e) {
throw new BaseException(DATABASE_ERROR);
}
}
}
작성한 메서드를 자세히 살펴보겠습니다.
1.4.1 sendCode(String email)
1.4.2 sendAuthEmail (String email, String authkey)
여기까지만 진행해도 이메일을 보낼 수 있지만, 추가적으로 이메일을 이미지와 템플릿을 이용해 꾸며주었습니다.
1.4.2 sendEmail 메서드 중 템플릿 관련 코드를 보겠습니다.
//템플릿에 전달할 데이터 설정 (타임리프 설정)
Context context = new Context();
context.setVariable("authKey", authKey); //Template에 전달할 데이터(authKey) 설정
//메일 내용 설정 : 템플릿 프로세스
String html = springTemplateEngine.process("index", context); //index.html
helper.setText(html, true);
helper.addInline("image", new ClassPathResource("templates/images/image-1.jpeg"));
따라서 전송할 html (index.html) 양식 안의 img 경로를 다음과 같이 설정해둔 후
<img src='cid:image'>
이메일 전송 전 addInline 메소드로 해당 cid를 적용했습니다.
helper.addInline("image", new ClassPathResource("templates/images/image-1.jpeg"));
이메일 템플릿 구현이 끝났으니, 아래 Redis 부분을 보겠습니다.
Redis(REmote Dictionary Server)는 NOSQL, 비 관계형 데이터베이스입니다. KEY와 VALUE 구조로 이루어져 있으며 처리가 빠르고 유효 시간을 정해 데이터가 남지 않도록 할 수 있습니다. 인증번호를 3분 동안만 저장하고 소멸시키기 위해 Redis를 도입했습니다.
redis:
host: AWS ElastiCache Redis 클러스터 리더 엔드포인트 //127.0.0.1 (local)
port: 6379
@Service
@RequiredArgsConstructor
public class RedisUtil {
private final StringRedisTemplate stringRedisTemplate;
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);
}
}
이렇게 SMTP와 ElastiCache(Redis)를 이용해 학교 인증용 이메일 인증코드 전송 기능을 구현해보았습니다.
인증코드 API를 호출하면, 아래 사진과 같이 이메일이 오는걸 확인할 수 있습니다.
감사합니다.
이메일이 학교 이메일인지 아닌지 확인하는 처리도 하셨나요??