[Spring Boot] 회원가입, 비밀번호 재설정 시 이메일로 인증 구현(SMTP)
2023. 5. 23. 01:56ㆍBackend/Spring
예전부터 프로젝트에 회원가입/비밀번호 재설정 시 이메일로 인증 받는 기능을 구현해보고 싶었는데, 드디어 구현해보게 됐다.
그 과정을 정리해보려 한다.
환경설정1 - Gradle에 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-mail:2.7.1'
Spring Boot Starter Mail 라이브러리를 임포트해준다.
환경설정2 - 네이버 메일 설정
'네이버 메일 > 환경설정> POP3/IMAP 설정' 으로 들어간다.
SMTP 서버명, SMTP 포트, 아이디 확인 후 아래 사진과 같이 환경 설정을 마친다.
환경설정3 - application.yml 설정
application.yml 파일에 따로 추가하지 않고 config 파일을 만들수도 있다.
하지만 계정 이메일과 비밀번호를 지키기 위해 application.yml 파일에 설정 정보를 추가해 준 뒤 config 파일들을 관리하는 디렉터리 밑에 mail config 파일을 따로 만들기로 했다.
application.yml 파일에 설정 정보는 다음과 같이 추가한다.
spring:
mail:
host: smtp.naver.com
port: 465
username: 아이디@naver.com
password: 비밀번호
properties:
mail.smtp.auth: true
mail.smtp.ssl.enable: true
mail.smtp.ssl.trust: smtp.naver.com
mail.smtp.starttls.enable: true
환경설정4 - MailConfig 파일 작성
package com.example.gabojago_server.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import java.util.Properties;
@Configuration
@PropertySource("classpath:application.yml")
public class MailConfig {
@Value("${spring.mail.username}")
private String id;
@Value("${spring.mail.password}")
private String password;
@Value("${spring.mail.host}")
private String host;
@Value("${spring.mail.port}")
private int port;
@Bean
public JavaMailSender javaMailService(){
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost(host); //smtp 서버 주소
javaMailSender.setUsername(id); //발신 메일 아이디
javaMailSender.setPassword(password); //발신 메일 비밀번호
javaMailSender.setPort(port); //smtp port
javaMailSender.setJavaMailProperties(getMailProperties()); //메일 인증서버 정보 가져오기
javaMailSender.setDefaultEncoding("UTF-8");
return javaMailSender;
}
private Properties getMailProperties(){
Properties properties = new Properties();
properties.setProperty("mail.transport.protocol", "smtp"); //프로토콜 설정
properties.setProperty("mail.smtp.auth", "true"); //smtp 인증
properties.setProperty("mail.smtp.starttls.enable", "true"); //smtp starttls 사용
properties.setProperty("mail.debug", "true"); //디버그 사용
properties.setProperty("mail.smtp.ssl.trust", "smtp.naver.com"); //ssl 인증 서버 주소
properties.setProperty("mail.smtp.ssl.enable", "true"); //ssl 사용
return properties;
}
}
메일 발송 구현 - Service(이메일 인증, 비밀번호 재설정) 작성
- createMessage - 인증코드를 포함한 메일 양식(내용) 작성
- createKey - 랜덤 8자리 인증코드 생성
- sendSimpleMessgae - 실제 메일 발송
package com.example.gabojago_server.service.mail;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.util.Random;
@PropertySource("classpath:application.yml")
@Slf4j
@RequiredArgsConstructor
@Service
public class EmailService {
private final JavaMailSender javaMailSender;
private final String ePw = createKey(); //인증번호
@Value("${spring.mail.username}")
private String id;
//메일 양식 작성
public MimeMessage createMessge(String to) throws MessagingException, UnsupportedEncodingException {
log.info("보내는 대상 : " + to);
log.info("인증 번호 : " + ePw);
MimeMessage message = javaMailSender.createMimeMessage();
message.addRecipients(MimeMessage.RecipientType.TO, to); //수신자
message.setSubject("가보자Go 인증 코드입니다."); //메일 제목
String msg = "";
msg += "<div style='margin:100px;'>";
msg += "<h1 style=\"font-size: 30px; padding-right: 30px; padding-left: 30px;\">가보자Go 이메일 인증 확인</h1>";
msg += "<p style=\"font-size: 17px; padding-right: 30px; padding-left: 30px;\">아래 확인 코드를 서비스 화면으로 돌아가 입력해주세요.</p>";
msg += "<div style=\"padding-right: 30px; padding-left: 30px; margin: 32px 0 40px;\"><table style=\"border-collapse: collapse; border: 0; background-color: #F4F4F4; height: 70px; table-layout: fixed; word-wrap: break-word; border-radius: 6px;\"><tbody><tr><td style=\"text-align: center; vertical-align: middle; font-size: 30px;\">";
msg += ePw;
msg += "</td></tr></tbody></table></div>";
message.setText(msg, "utf-8", "html"); //내용, charset타입, subtype
message.setFrom(new InternetAddress(id, "gabojago_Admin")); //발신자 메일주소, 발신자 이름
return message;
}
//인증 코드 8자리 만들기
public static String createKey(){
StringBuffer key = new StringBuffer();
Random random = new Random();
for(int i = 0; i<8; i++){
int idx = random.nextInt(3); //0~2 경우를 랜덤
switch (idx){
case 0:
key.append((char) ((int) random.nextInt(26) + 97)); //a~z
break;
case 1:
key.append((char) ((int) random.nextInt(26) + 65)); //A~Z
break;
case 2:
key.append(random.nextInt(10)); //0~9
break;
}
}
return key.toString();
}
//메일 발송
public String sendSimpleMessage(String to) throws Exception {
MimeMessage message = createMessge(to);
try{
javaMailSender.send(message);
}catch (MailException es){
es.printStackTrace();
throw new IllegalAccessException();
}
return ePw; //메일로 보냈던 인증 코드를 서버로 반환
}
}
아래는 임시 비밀번호를 send하는 ChangePwEmailService
package com.example.gabojago_server.service.mail;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.util.Random;
@Slf4j
@Service
@RequiredArgsConstructor
public class ChangePwEmailService {
private final JavaMailSender emailsender;
private String ePw = createKey(); //인증번호
@Value("${spring.mail.username}")
private String id;
private static final String SPECIAL_CHARS = "!@#$%^&*";
public MimeMessage createMessage(String to) throws MessagingException, UnsupportedEncodingException {
log.info("보내는 대상 : " + to);
log.info("인증 번호 : " + ePw);
MimeMessage message = emailsender.createMimeMessage();
message.addRecipients(MimeMessage.RecipientType.TO, to); //수신자
message.setSubject("가보자Go 임시 비밀번호입니다."); //메일 제목
String msg = "";
msg += "<div style='margin:100px;'>";
msg += "<h1 style=\"font-size: 30px; padding-right: 30px; padding-left: 30px;\">가보자Go 비밀번호 재설정을 위한 임시 비밃번호입니다.</h1>";
msg += "<p style=\"font-size: 17px; padding-right: 30px; padding-left: 30px;\">아래 임시 비밀번호로 로그인 후 새로운 비밀번호로 변경 부탁드립니다.</p>";
msg += "<div style=\"padding-right: 30px; padding-left: 30px; margin: 32px 0 40px;\"><table style=\"border-collapse: collapse; border: 0; background-color: #F4F4F4; height: 70px; table-layout: fixed; word-wrap: break-word; border-radius: 6px;\"><tbody><tr><td style=\"text-align: center; vertical-align: middle; font-size: 30px;\">";
msg += ePw;
msg += "</td></tr></tbody></table></div>";
message.setText(msg, "utf-8", "html");
message.setFrom(new InternetAddress(id, "gabojago_Admin"));
return message;
}
//인증 코드 만들기
public static String createKey(){
StringBuffer key = new StringBuffer();
Random random = new Random();
boolean hasUpperCase = false;
boolean hasLowerCase = false;
boolean hasNumber = false;
boolean hasSpecialChar = false;
while(key.length()<8 || !(hasLowerCase && hasUpperCase && hasNumber && hasSpecialChar)){
int idx = random.nextInt(4);
switch (idx){
case 0:
if(!hasLowerCase) {
key.append((char) ((int) random.nextInt(26) + 97));
hasLowerCase = true;
}
break;
case 1:
if(!hasUpperCase) {
key.append((char) ((int) random.nextInt(26) + 65));
hasUpperCase = true;
}
break;
case 2:
if(!hasNumber) {
key.append(random.nextInt(10));
hasNumber = true;
}
break;
case 3:
if(!hasSpecialChar) {
int charIdx = random.nextInt(SPECIAL_CHARS.length());
char specialChar = SPECIAL_CHARS.charAt(charIdx);
key.append(specialChar);
hasSpecialChar = true;
}
break;
}
}
return key.toString();
}
//메일 발송
public String sendSimpleMessage(String to) throws Exception {
MimeMessage message = createMessage(to);
try{
emailsender.send(message);
}catch (MailException es){
es.printStackTrace();
throw new IllegalAccessException();
}
return ePw;
}
}
메일 발송 구현 - Controller 작성
@PostMapping("/signup/mailConfirm")
@ResponseBody
public ResponseEntity<String> mailConfirm(@RequestBody String email) throws Exception {
if(authService.findMember(email)) return ResponseEntity.ok(NormalResponse.duplicated().getStatus());
String code = signupEmailService.sendSimpleMessage(email);
return ResponseEntity.ok(code);
}
@PostMapping("/login/findPw")
@ResponseBody
public ResponseEntity<NormalResponse> findPw(@RequestBody String email) throws Exception {
if(authService.findMember(email)){
String newPassword = pwEmailService.sendSimpleMessage(email);
authService.changeTempPw(email, newPassword);
return ResponseEntity.ok(NormalResponse.success());
}else{
return ResponseEntity.ok(NormalResponse.fail());
}
}
참고
https://velog.io/@leesomyoung/SpringBoot-SMTP-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%9D%B8%EC%A6%9D-%EB%84%A4%EC%9D%B4%EB%B2%84
https://velog.io/@kyungwoon/%EC%9D%B4%EB%A9%94%EC%9D%BC%EB%A1%9C-%EC%9D%B8%EC%A6%9D-%EC%BD%94%EB%93%9C-%EB%B3%B4%EB%82%B4%EA%B8%B0SpringBoot-SMTP