[Spring Boot + SMTP] 이메일 인증 구현
1. 개발환경
Java 17
Spring Boot 3.1.8
Gradle 8.5
2. 의존성 추가
// SMTP
implementation 'org.springframework.boot:spring-boot-starter-mail'
// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
SMTP와 Redis의 의존성을 build.gradle에 추가해 줍니다.
3. Gmail SMTP 설정
3-1
구글로그인 후 지메일로 들어갑니다.
3-2
오른쪽 상단의 톱니바퀴 버튼을 누른 뒤 <모든 설정 보기> 버튼을 클릭합니다.
3-3
전달 및 POP/IMAP를 눌러주신 후 사진과 같이 체크한 뒤 <변경사항 저장> 버튼을 눌러 저장해 줍니다.
3-4
이제 우측 상단의 프로필버튼을 눌러서 <Google 계정 관리>를 클릭해 줍니다.
3-5
이 화면이 뜰 텐데 여기서 보안을 클릭해 줍니다
3-6
스크롤을 내린 뒤 Google에 로그인하는 방법 탭에서 <2단계 인증>을 클릭해 줍니다.
3-7
스크롤을 제일 밑으로 내리면 앱 비밀번호 탭이 있습니다. 클릭해 줍니다.
3-8
사용할 이름을 적고 만들기를 눌러줍니다
3-9
다음과 같이 비밀번호가 뜰 텐데 추후 application.properties에 작성해야 하니 따로 저장해 둡니다.
4. Redis 설치
https://ngwdeveloper.tistory.com/94
[Redis] Redis 설치 방법
Redis NoSQL DB의 한 종류이며 우리가 흔히 사용하는 MYSQL, Orcal DB, PostgreSQL 등 RDBMS와 다르게 NoSQL DB이다. 그렇다면 무슨 차이이고 어느 상황에 사용해야 할까? RDBMS와 NoSQL의 차이 RDBMS (관계형 DB) - 데
ngwdeveloper.tistory.com
설치가 안되어있으신 분들은 위 링크에 들어가서 따라 해 주시면 됩니다. 만약 설치가 되어있다면 넘어가셔도 됩니다.
5. application.properties 작성
# Email Properties
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=지메일주소@gmail.com
spring.mail.password=2차비밀번호
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
SMTP의 properties 작성법입니다. 아까 발급받은 본인의 지메일 주소와 저장해 둔 비밀번호를 입력해 주시면 됩니다.
#Redis
spring.data.redis.host = localhost
spring.data.redis.port = 6379
만약 Redis설치를 방금 하시고 설정이 되지 않으신 상태라면 위 코드를 추가로 properties에 넣어줍니다.
6. RedisConfig 작성
@Getter
@Configuration
@RequiredArgsConstructor
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
// 내장 / 외부 Redis 연결
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory(host, port);
}
/*
* RedisConnection 에서 넘겨준 byte 값을 직렬화 RedisTemplate 은
* Redis 데이터를 저장하고 조회하는 기능을 하는 클래스 REdis cli 를 사용해 Redis 데이터를 직접 조회할때,
* Redis 데이터를 문자열로 반환하기 위한 설정
*/
@Bean
public RedisTemplate<String, String> redisTemplate(){
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
7. RedisUtil 작성
@Service
@RequiredArgsConstructor
public class RedisUtil {
private final StringRedisTemplate redisTemplate;
public String getData(String key) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}
public boolean existData(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
public void setDataExpire(String key, String value, long duration) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
Duration expireDuration = Duration.ofSeconds(duration);
valueOperations.set(key, value, expireDuration);
}
public void deleteData(String key) {
redisTemplate.delete(key);
}
}
8. EmailAuthResponseDto 작성
@Getter
public class EmailAuthResponseDto {
private boolean success;
private String responseMessage;
public EmailAuthResponseDto(boolean success, String responseMessage){
this.success = success;
this.responseMessage = responseMessage;
}
}
응답결과를 담을 DTO입니다. 성공, 실패 여부와 메시지를 담아서 반환합니다.
9. EmailController 작성
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/email")
public class EmailController {
private final EmailService emailService;
// 인증번호 전송
@GetMapping("/auth")
public EmailAuthResponseDto sendAuthCode(@RequestParam String address) {
return emailService.sendEmail(address);
}
// 인증번호 검증
@PostMapping("/auth")
public EmailAuthResponseDto checkAuthCode(@RequestParam String address, @RequestParam String authCode) {
return emailService.validateAuthCode(address, authCode);
}
}
사용할 컨트롤러입니다. 인증번호를 전송하고 검증하는 두 개의 컨트롤러만 사용합니다. 필요한 게 있다면 추가로 작성하셔서 사용하시면 됩니다.
10. EmailService 작성
@Service
@RequiredArgsConstructor
public class EmailService {
@Value("${spring.mail.username}")
private String senderEmail;
private final JavaMailSender mailSender;
private final RedisUtil redisUtil;
public EmailAuthResponseDto sendEmail(String toEmail) {
if (redisUtil.existData(toEmail)) {
redisUtil.deleteData(toEmail);
}
try {
MimeMessage emailForm = createEmailForm(toEmail);
mailSender.send(emailForm);
return new EmailAuthResponseDto(true, "인증번호가 메일로 전송되었습니다.");
} catch (MessagingException | MailSendException e) {
return new EmailAuthResponseDto(false, "메일 전송 중 오류가 발생하였습니다. 다시 시도해주세요.");
}
}
private MimeMessage createEmailForm(String email) throws MessagingException {
String authCode = String.valueOf(ThreadLocalRandom.current().nextInt(100000, 1000000));
MimeMessage message = mailSender.createMimeMessage();
message.setFrom(senderEmail);
message.setRecipients(MimeMessage.RecipientType.TO, email);
message.setSubject("인증코드입니다.");
message.setText(setContext(authCode), "utf-8", "html");
redisUtil.setDataExpire(email, authCode, 10 * 60L); // 10분
return message;
}
private String setContext(String authCode) {
String body = "";
body += "<h4>" + "인증 코드를 입력하세요." + "</h4>";
body += "<h2>" + "[" + authCode + "]" + "</h2>";
return body;
}
public EmailAuthResponseDto validateAuthCode(String email, String authCode) {
String findAuthCode = redisUtil.getData(email);
if (findAuthCode == null) {
return new EmailAuthResponseDto(false, "인증번호가 만료되었습니다. 다시 시도해주세요.");
}
if (findAuthCode.equals(authCode)) {
return new EmailAuthResponseDto(true, "인증 성공에 성공했습니다.");
} else {
return new EmailAuthResponseDto(false, "인증번호가 일치하지 않습니다.");
}
}
}
서비스 코드입니다. message에 담을 내용을 set해준 뒤 반환하는 메서드, 인증코드를 입력하도록 바디에 넣어서 반환하는 메서드, 인증번호를 검증하는 메서드로 이루어져 있습니다.
11. 테스트
인증번호를 수신할 이메일을 address 값으로 지정해 준 뒤 GET 해주면 정상적으로 보내졌다는 true와 메시지, 200 코드가 반환됩니다.
메시지함에 들어가도 정상적으로 수신된 걸 확인할 수 있습니다.
RequestParam방식으로 address에는 인증할 이메일, authCode에 인증번호를 넣고 POST 해본 결과 정상적으로 인증에 성공된 것을 확인할 수 있습니다.