이메일을 통해서 인증코드를 전송하려면
어떻게 해야 할까?
SMTP 의존성 추가하기
--
SMTP란?
Simple Mail Transfer Protocol의 약자로
인터넷을 통해 이메일 메시지를 주고받을 때에 사용되는 통신 프로토콜이다.
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-mail'
}
해당 의존성은
Spring Boot 애플리케이션에서 이메일을 쉽게 전송할 수 있도록 지원하는 기능을 가지고 있다.
주요 기능
- JavaMailSender : 이메일을 전송하기 위한 기본 인터페이스 (간편하게 이메일을 구성 및 전송)
- MailSenderAutoConfiguartion : 이메일 전송에 대해 간편하게 설정
- MimeMessageHelper : 첨부 파일, HTML 형식의 이메일, 인코딩 등의 이메일 기능
--
구글 앱 비밀번호 생성하기
--
1. 구글 계정 관리 페이지 이동
2. 앱 비밀번호 페이지 이동
3. 앱 비밀번호 이름을 작성하여 4 * 4로 이루어진 비밀번호 생성
해당 비밀번호를 application.yml 에서 기본값 세팅할 때
spring.mail.password: 부분에 작성해 주면 된다.
--
기본값 세팅하기
--
위의 의존성 ('spring-boot-starter-mail')을 사용하면 자동으로 spplication.yml 파일에 작성한 이메일 설정 값을 기본값으로 사용하여 설정된 값들을 바탕으로 이메일을 전송할 수 있다.
application.yml
spring:
mail:
host: <SMTP 서버의 호스트 이름>
port: <SMTP 서버의 포트 번호>
username: <SMTP 서버에서 로그인할 사용자 이름 / abcdefg11@gmail.com 이 본인 이메일이면 abcdefg11를 작성>
password: <SMTP 서버에서 로그인할 앱 비밀번호 / 생성한 앱 비밀번호>
properties:
mail:
smtp:
auth: <SMTP 서버 인증 여부 / 기본값 : false>
timeout: <SMTP 서버와의 연결에 대한 타임아웃 값 (ms 단위 / 기본값 : 제한 없음 = 무한)
starttls:
enable: <STARTTLS 프로토콜을 사용하려 SMTP 서버와의 통신에 대해 암호화 여부 / 기본값 : false>
default-encoding: <이메일의 기본 인코딩 설정>
spring:
mail:
host: smtp.gmail.com
port: 587
username: abcdefg11
password: aaaa bbbb cccc dddd
properties:
mail:
smtp:
auth: true
timeout: 5000
starttls:
enable: true
해당 기본 세팅값은 개인정보(유출X)이기 때문에 GitHub에 올린다면 .gitingore에 application.yml파일을 추가해야 한다.
--
EmailCode.java [ 인증코드를 검증하기 위한 Entity ]
--
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Entity
public class EmailCode {
@Id
private String email; // 이메일
private String code; // 인증코드
private LocalDateTime expiryDate; // 인증 만료 시간
// 인증코드 변경
public void changeCode(String code, LocalDateTime expiryDate){
this.code = code;
this.expiryDate = expiryDate;
}
}
- email : 인증코드를 발송한 수신 이메일
- code : 이메일로 전달한 인증 코드
- expiryDate : 인증 코드를 발급 후 해당 인증 코드가 만료되는 시각
코드 설명
- email을 기본키(id)로 지정하여 중복된 email을 저장할 수 없게 한다.
- DB에 해당 이메일로 발급한 데이터가 존재할 때
(인증코드를 재발급하는 경우 or DB에 해당 이메일 데이터가 아직 제거되지 않는 경우)
인증코드를 다시 받은 경우에 ChangeCode() 메서드를 통해
다시 발급받은 인증코드로 교체 및 인증 만료 시간 재설정
--
EmailCodeRepository.java
--
public interface EmailCodeRepository extends JpaRepository<EmailCode, String> {
Optional<EmailCode> findByEmail(String email);
// 주어진 시간 (현재시각을 고정적으로 보낼 예정) 보다 이전 시간인 데이터들은 삭제
void deleteByExpiryDateBefore(LocalDateTime now);
}
코드 설명
- 인증 코드를 발급하여 DB에 저장된 특정 이메일에 대한 데이터를 조회하기 위한 findByEmail();
- 인증 코드 만료 기간이 지났음에도 아직 DB에 남아있는 데이터를 삭제하기 위한 deleteByExpiryDateBefor();
(스케쥴러에서 사용할 예정)
--
EmailScheduler.java [ 코드 발급 후 인증하지 않아 남아있는 데이터 자동 제거 스케쥴러 ]
--
@Component
@RequiredArgsConstructor
public class EmailScheduler {
private final EmailCodeRepository emailCodeRepository;
@Scheduled(fixedRate = 3600000) // 1시간마다 실행
public void cleanUpExpiredCodes() {
// 인증 코드 만료시간이 지난 데이터 삭제
emailCodeRepository.deleteByExpiryDateBefore(LocalDateTime.now());
}
}
--
EmailDto.java [ 클라이언트(사용자)로부터 이메일, 인증코드를 받아오기 위한 DTO ]
--
public class EmailDto {
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public static class EmailRequest {
private String email;
}
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public static class EmailCodeRequest {
private String email;
private String code;
}
}
- EmailRequest : 인증 코드를 발송해 주기 위해 수신할 이메일 요청 DTO
- EmailCodeRequest : 인증한 이메일과 인증 코드를 확인하기 위한 요청 DTO
--
단순한 텍스트만 이메일로 발송하기 (Controller, Service)
--
EmailService.java
@Service
@RequiredArgsConstructor
@Slf4j
public class EmailService {
private final JavaMailSender javaMailSender;
@Transactional
public void sendSimpleEmail(EmailDto.EmailRequest request) {
// 단순 문자 이메일을 보내기 위한 객체 생성
SimpleMailMessage message = new SimpleMailMessage();
// 이메일 제목
message.setSubject("이메일이 발신되었습니다.");
// 수신자 이메일
message.setTo(request.getEmail());
// 이메일 내용
message.setText("내용 0000");
// 이메일 발송
javaMailSender.send(message);
}
}
EmailController.java
@RestController
@RequiredArgsConstructor
@Slf4j
public class EmailController {
private final EmailService emailService;
@PostMapping("/email/test")
public String simpleEmail(@RequestBody EmailDto.EmailRequest request) {
emailService.sendSimpleEmail(request);
return "심플 이메일 발송 완료";
}
}
--
HTML 형식으로 이메일 발송하기 (HTML, Controller, Service)
--
index.html
. . .
인증 번호 : <span th.text="${code}"></span>
. . .
타임리프를 이용하여 HTML파일을 작성했으며 인증 번호는 변수(code)로 지정하였다.
EmailService.java
@Service
@RequiredArgsConstructor
@Slf4j
public class EmailService {
private final JavaMailSender javaMailSender;
private final SpringTemplateEngine springTemplateEngine;
private final EmailCodeRepository emailCodeRepository;
// HTML 이메일 보내기
@Transactional
public void sendHTMLEmail(EmailDto.EmailRequest request) {
MimeMessage message = javaMailSender.createMimeMessage();
String code = createRandomCode();
// 해당 이메일이 이전에 인증코드를 받은 내역이 남아있는지 확인
EmailCode emailCode = emailCodeRepository.findByEmail(request.getEmail()).orElse(null);
// 만약 없다면 정상적으로 생성 / 있다면 기존 인증코드를 새로운 코드로 교체 & 만료시간 갱신
if(emailCode == null){
emailCode = new EmailCode(request.getEmail(), code, createExpiryDate());
} else {
emailCode.changeCode(code, createExpiryDate());
}
try {
// 이메일 제목
message.setSubject("[RealMoment] 이메일 인증");
// 이메일 내용 (HTML 내용으로 세팅한 값을 지정)(UTF-8 = 인코딩, html = 해당 내용이 html)
message.setText(setContext(code), "UTF-8", "html");
// 수신자 이메일을 추가하는 부분
message.addRecipients(MimeMessage.RecipientType.TO, request.getEmail());
// 해당 메시지를 이메일 보냄
javaMailSender.send(message);
// DB에 해당 정보 저장
emailCodeRepository.save(emailCode);
} catch (MessagingException e){
e.printStackTrace();
}
}
// 이메일 인증코드 인증 (+만료시간 체크)
@Transactional
public String codeCheck(EmailDto.EmailCodeRequest request){
EmailCode emailCode = emailCodeRepository.findByEmail(request.getEmail()).orElse(null);
if (emailCode != null && emailCode.getExpiryDate().isAfter(LocalDateTime.now())){
if (emailCode.getCode().equals(request.getCode())){
emailCodeRepository.delete(emailCode);
return "인증 성공";
} else {
return "인증 실패";
}
} else {
return "기간 만료 or 잘못된 이메일";
}
}
// HTML 이메일 내용 세팅
@Transactional
public String setContext(String code){
// 타임리프의 Context이다.
Context context = new Context();
// html에서 변수 ${code}에 들어갈 변수 명과 값을 작성
context.setVariable("code", code);
// html의 경로(이름)과 context를 작성
return springTemplateEngine.process("index", context);
}
// 6자리 랜덤 인증 코드 생성하기
@Transactional
public String createRandomCode() {
String[] sign = new String[] {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "y", "z",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "Y", "Z"
};
StringBuffer key = new StringBuffer();
for(int i = 0; i < 6; i++){
int randomIndex = (int)(Math.random() * sign.length);
key.append(sign[randomIndex]);
}
return key.toString();
}
// 인증 만료 시간 생성
public LocalDateTime createExpiryDate(){
// 현재 시간에서 5분 추가된 시간
LocalDateTime expiryDate = LocalDateTime.now().plus(5, ChronoUnit.MINUTES);
return expiryDate;
}
}
EmailController.java
@RestController
@RequiredArgsConstructor
@Slf4j
public class EmailController {
private final EmailService emailService;
// 인증코드 이메일 전송 (html파일 전송)
@PostMapping("/email/html")
public String htmlEmail(@RequestBody EmailDto.EmailRequest request) {
emailService.sendHTMLEmail(request);
return "인증번호 이메일 발송 완료";
}
// 이메일 인증코드 검증(확인)
@PostMapping("/email/code/check")
public ResponseEntity<String> codeCheck(@RequestBody EmailDto.EmailCodeRequest request){
String result = emailService.codeCheck(request);
if (result.equals("인증 성공")){
return ResponseEntity.ok("GOOD");
} else if (result.equals("인증 실패")){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("BAD");
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("TIME_OUT_or_BAD_EMAIL");
}
}
}
--
'Spring Boot' 카테고리의 다른 글
Spring boot에 WebSocket 사용하기 [ 간단한 채팅 용 ] (0) | 2024.11.27 |
---|---|
[IntelliJ] finished with non-zero exit value 1 에러 해결 방법 (0) | 2024.11.26 |
spring security에서 발생하는 예외 처리하기 (0) | 2024.05.27 |
스케줄러(@Scheduled)를 이용하여 특정 로직을 자동으로 동작하게 하기 (0) | 2024.05.03 |
validation을 이용하여 유효성 검사하기 [ feat. 회원가입 ] (0) | 2024.04.26 |