
팀 프로젝트를 진행하면서 이메일 인증을 넣어서 무분별한 회원가입을 막자는 의견이 나와서 이메일 인증 로직을 개발하게 되었다.
MacOS
Java 22
Spring Boot 3.3.3
이메일 인증은 다양한 방식으로 할 수 있다, 대표적으로 네이버나 구글 SMTP 서버가 있고. 또 Firebase로 개발할 수도 있다.
하지만 이번에는 구글 SMTP를 활용하여 개발하기로 했다.
SMTP(Simple Mail Transfer Protocol)란 인터넷을 통해 이메일을 보내고 받는 데 사용되는 통신 프로토콜이다. 말 그대로 이메일을 주고 받을 때 이 SMTP라는 친구를 통해 한다는 소리이다.
그렇다면 내가 직접 SMTP를 구축할 수 있지 않나 라는 생각이 들었지만 그것은 나같은 초보 개발자에게 너무나 끔찍하고 고통스러운 것이기에 이미 시중에 있는 구글 SMTP를 사용하겠다.

먼저 구글 이메일로 들어가서 우측 상단에 빠른 설정창을 열어준다. 그러면 맨 위에 모든 설정 보기 라는 버튼이 있는데 이를 클릭해준다.
그럼 아래와 같은 화면이 보일 것이다.

여기서 전달 및 POP/IMAP을 클릭해 준다.

위 화면과 똑같이 설정을 변경해준 후에 맨 하단에 변경사항 저장을 클릭한 후 나와준다.
참고로 해당 설정을 한 계정에 이메일을 기억해 둬야 한다, 나중에 이를 통해 이메일 전송을 하기 때문이다.
구글 계정 관리에 접속해준다.

왼쪽 메뉴에서 보안을 선택한 후 2단계 인증을 클릭해 준다.

가장 하단에 있는 앱 비밀번호를 선택한다.

그러면 앱 이름을 입력하는 칸이 나오는데 각자 프로젝트 이름이나 원하는 이름을 기입한 후에 만들기를 클릭해 준다.

그러면 다음과 같이 보안키가 생성된다. 복사해서 메모장 같은 곳에 따로 보관해 주자. 외부로 유출되면 안되니 신경을 써주는 것이 좋다.

gradle에 의존성을 추가해준다.
implementation 'org.springframework.boot:spring-boot-starter-mail'
application.yml 파일에 다음과 같이 추가해준다.
spring:
mail:
host: smtp.gmail.com
username: (이메일)
password: (앱 비밀번호)
properties:
mail.smtp.auth: true
mail.smtp.starttls.enable: true
SMTP 설정을 해줘야 하기에, EmailConfig 클래스에 다음과 같은 코드를 작성한다.
@Configuration
public class EmailConfig {
/* set important data */
@Value("${spring.mail.username}") private String username;
@Value("${spring.mail.password}") private String password;
@Bean
public JavaMailSender mailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.gmail.com");
mailSender.setPort(587); // TLS port
mailSender.setUsername(username);
mailSender.setPassword(password);
/* Use Properties Object to set JavaMailProperties */
Properties javaMailProperties = new Properties();
javaMailProperties.put("mail.transport.protocol", "smtp");
javaMailProperties.put("mail.smtp.auth", "true");
javaMailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
javaMailProperties.put("mail.smtp.starttls.enable", "true");
javaMailProperties.put("mail.debug", "true");
javaMailProperties.put("mail.smtp.ssl.trust", "smtp.gmail.com");
javaMailProperties.put("mail.smtp.ssl.protocols", "TLSv1.3"); // TLS v1.3을 사용
mailSender.setJavaMailProperties(javaMailProperties);
return mailSender;
}
}
EmailService 클래스 안에 다음과 같이 코드를 작성한다. 코드 설명은 주석을 참고하면 된다.
@Service
@RequiredArgsConstructor
public class EmailService {
private final JavaMailSender javaMailSender;
private final RedisConfig redisConfig;
private int authNumber;
/* 이메일 인증에 필요한 정보 */
@Value("${spring.mail.username}")
private String serviceName;
/* 랜덤 인증번호 생성 */
public void makeRandomNum() {
Random r = new Random();
String randomNumber = "";
for(int i = 0; i < 6; i++) {
randomNumber += Integer.toString(r.nextInt(10));
}
authNumber = Integer.parseInt(randomNumber);
}
/* 이메일 전송 */
public void mailSend(String setFrom, String toMail, String title, String content) {
MimeMessage message = javaMailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message,true,"utf-8");
helper.setFrom(setFrom); // service name
helper.setTo(toMail); // customer email
helper.setSubject(title); // email title
helper.setText(content,true); // content, html: true
javaMailSender.send(message);
} catch (MessagingException e) {
e.printStackTrace(); // 에러 출력
}
// redis에 3분 동안 이메일과 인증 코드 저장
ValueOperations<String, String> valOperations = redisConfig.redisTemplate().opsForValue();
valOperations.set(toMail, Integer.toString(authNumber), 180, TimeUnit.SECONDS);
}
/* 이메일 작성 */
public String joinEmail(String email) {
makeRandomNum();
String customerMail = email;
String title = "회원 가입을 위한 이메일입니다!";
String content =
"이메일을 인증하기 위한 절차입니다." +
"<br><br>" +
"인증 번호는 " + authNumber + "입니다." +
"<br>" +
"회원 가입 폼에 해당 번호를 입력해주세요.";
mailSend(serviceName, customerMail, title, content);
return Integer.toString(authNumber);
}
public BaseResponse checkAuthNum(String email, String authNum) {
ValueOperations<String, String> valOperations = redisConfig.redisTemplate().opsForValue();
String code = valOperations.get(email);
if (Objects.equals(code, authNum)) {
return BaseResponse.ok("인증 성공");
} else return BaseResponse.of(HttpStatus.BAD_REQUEST,"인증 실패");
}
}
그 후에 Controller에 아래 코드를 붙여넣으면 된다.
@RestController
@RequestMapping("/email")
@RequiredArgsConstructor
@Tag(name = "이메일", description = "이메일 API")
public class EmailController {
private final EmailService emailService;
@PostMapping("/send")
@Operation(summary = "이메일 전송")
public BaseResponse sendEmail(@RequestBody EmailSendRequest request) {
emailService.joinEmail(request.getEmail());
return BaseResponse.ok("이메일 전송 성공");
}
@PostMapping("/check")
@Operation(summary = "이메일 인증")
public BaseResponse checkEmail(@RequestBody EmailCheckRequest request) {
return emailService.checkAuthNum(request.getEmail(),request.getAuthNum());
}
}
/send를 통해 이메일을 전송하고 /check는 전송된 이메일을 인증할 수 있다.
인증을 위해 생성된 코드는 Redis에 3분 간 저장되어 있다가 삭제된다. 코드를 계속 보관하고 있지 않는 이유는 보안을 위해 지속적으로 삭제 및 변경하기 위해서 이다.
Redis 배포는 아래 글에 링크를 달아두었으니 참고하기를 바란다. 필자는 Redis가 배포 되었다는 가정 하에 진행하도록 하겠다.
Redis 설정을 위해 yml에 다음과 같이 추가해준다.
spring:
redis:
host: localhost
port: 6379
그 후에 RedisConfig 클래스를 추가, 아래와 같은 코드를 넣어준다.
@EnableRedisRepositories
@RequiredArgsConstructor
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private Integer port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<String, String> redisTemplate() {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
이러면 이메일 인증 로직은 모두 완성되었다.
서버를 실행시킨 후에 Postman으로 테스트를 해보면 잘 작동하는 것을 확인할 수 있다.

그리고 실제로 이메일이 전송된다.

인증번호를 통해 인증까지 가능한 것을 테스트 했다.


멋있네요