프로젝트를 진행하던 중에 회원에게 메일을 발송하는 기능을 구현해야겠다는 생각이 들었다.
회원가입 시 이메일 인증을 하고, 비밀번호를 분실했을 때 임시 비밀번호를 발송하는 기능을 구현하였다.
메일을 발송하는 방법은 여러가지가 있지만 Gmail의 SMTP 서버를 활용하여 이메일을 발송하는 방법을 이용하여 기능을 구현해보았다.
Spring Boot 2.7.3
Java 11
IntelliJ
Gmail의 SMTP 서버를 활용하기 위한 절차가 필요하다.
구글 계정 관리에 들어가서 보안 탭을 클릭한다.
2단계 인증을 설정하지 않았다면 사용하는 것으로 설정을 변경한다.
아래 이미지를 참고하여 앱 비밀번호를 설정한다.
앱 선택 목록에서 메일을 선택한다.
기기 선택 목록에서 기타를 선택한 후 기기 이름을 입력한다(원하는대로 작성).
생성 버튼을 눌러 앱 비밀번호를 생성한다.
16자리의 앱 비밀번호를 확인합니다. 앱 비밀번호는 노출되지 않도록 주의하고, 개인 공간에 복사해둔다.
확인버튼을 눌러 완료한다.
Gmail 설정에 들어가서 전달 및 POP/IMAP 탭으로 들어간다.
모든 메일에 POP를 활성화 하기를 선택한다.
IMAP 사용을 선택한다.
변경사항을 저장한다.
build.gradle
수정dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-mail'
// 아래 2개는 thymeleaf를 사용하는 경우에만 추가
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
...
}
application.yml
수정spring:
mail:
host: smtp.gmail.com # 1
port: 587 # 2
username: ${mail.username} # 3
password: ${mail.password} # 4
properties:
mail:
smtp:
auth: true # 5
timeout: 5000 # 6
starttls:
enable: true # 7
SMTP 서버 호스트
SMTP 서버 포트
SMTP 서버 로그인 아이디: 발신자 (이메일이 test@gmail.com 이면 test가 해당)
SMTP 서버 로그인 패스워드: 앱 비밀번호
사용자 인증 시도 여부 (기본값 : false)
Socket Read Timeout 시간(ms) (기본값 : 무한대)
StartTLS 활성화 여부 (기본값 : false)
메일 정보와 앱 비밀번호가 유출되는 것을 대비하기 위해
application.yml
에서 username과 password를 직접적으로 쓰지 않고mail.username
,mail.password
와 같이 작성하여 환경변수를 설정해준다.
Edit Configurations에 들어간다.
이미지를 참고하여 환경변수를 설정해준다.
(이미지는 이메일이 test@gmail.com 이고 앱 비밀번호가 qwerasdfzxcvqwer 일 때의 예이다)
만약 환경변수를 설정하지 않고 바로 사용하고 싶다면 위 설정을 하지 않고 application.yml
파일을 아래와 같이 작성한다.
spring:
mail:
host: smtp.gmail.com
port: 587
username: test
password: qwerasdfzxcvqwer
properties:
mail:
smtp:
auth: true
timeout: 5000
starttls:
enable: true
이메일 인증 코드와 임시 비밀번호를 동일한 메서드를 이용하여 발급하였다.
package server.email.entity;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class EmailMessage {
private String to;
private String subject;
private String message;
}
to
: 수신자
subject
: 메일 제목
message
: 메일 내용
package server.email.dto;
import lombok.Getter;
@Getter
public class EmailPostDto {
private String email;
}
package server.email.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class EmailResponseDto {
private String code;
}
package server.email.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;
import server.email.entity.EmailMessage;
import server.user.service.UserService;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.Random;
@Slf4j
@Service
@RequiredArgsConstructor
public class EmailService {
private final JavaMailSender javaMailSender;
private final SpringTemplateEngine templateEngine;
private final UserService userService;
public String sendMail(EmailMessage emailMessage, String type) {
String authNum = createCode();
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
if (type.equals("password")) userService.SetTempPassword(emailMessage.getTo(), authNum);
try {
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8");
mimeMessageHelper.setTo(emailMessage.getTo()); // 메일 수신자
mimeMessageHelper.setSubject(emailMessage.getSubject()); // 메일 제목
mimeMessageHelper.setText(setContext(authNum, type), true); // 메일 본문 내용, HTML 여부
javaMailSender.send(mimeMessage);
log.info("Success");
return authNum;
} catch (MessagingException e) {
log.info("fail");
throw new RuntimeException(e);
}
}
// 인증번호 및 임시 비밀번호 생성 메서드
public String createCode() {
Random random = new Random();
StringBuffer key = new StringBuffer();
for (int i = 0; i < 8; i++) {
int index = random.nextInt(4);
switch (index) {
case 0: key.append((char) ((int) random.nextInt(26) + 97)); break;
case 1: key.append((char) ((int) random.nextInt(26) + 65)); break;
default: key.append(random.nextInt(9));
}
}
return key.toString();
}
// thymeleaf를 통한 html 적용
public String setContext(String code, String type) {
Context context = new Context();
context.setVariable("code", code);
return templateEngine.process(type, context);
}
}
sendMail()
메서드를 사용하기 위해 type
값을 받도록 하였다. package server.email.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import server.email.dto.EmailPostDto;
import server.email.dto.EmailResponseDto;
import server.email.entity.EmailMessage;
import server.email.service.EmailService;
@RequestMapping("/send-mail")
@RestController
@RequiredArgsConstructor
public class EmailController {
private final EmailService emailService;
// 임시 비밀번호 발급
@PostMapping("/password")
public ResponseEntity sendPasswordMail(@RequestBody EmailPostDto emailPostDto) {
EmailMessage emailMessage = EmailMessage.builder()
.to(emailPostDto.getEmail())
.subject("[SAVIEW] 임시 비밀번호 발급")
.build();
emailService.sendMail(emailMessage, "password");
return ResponseEntity.ok().build();
}
// 회원가입 이메일 인증 - 요청 시 body로 인증번호 반환하도록 작성하였음
@PostMapping("/email")
public ResponseEntity sendJoinMail(@RequestBody EmailPostDto emailPostDto) {
EmailMessage emailMessage = EmailMessage.builder()
.to(emailPostDto.getEmail())
.subject("[SAVIEW] 이메일 인증을 위한 인증 코드 발송")
.build();
String code = emailService.sendMail(emailMessage, "email");
EmailResponseDto emailResponseDto = new EmailResponseDto();
emailResponseDto.setCode(code);
return ResponseEntity.ok(emailResponseDto);
}
}
아래는 발송 메일 본문에 html
를 적용할 경우에만 참고하면 된다.
위치: resources/templates/email.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div style="margin:100px;">
<h1> 안녕하세요.</h1>
<h1> 개발자를 위한 면접 준비 플랫폼 SAVIEW 입니다.</h1>
<br>
<p> 아래 코드를 회원가입 창으로 돌아가 입력해주세요.</p>
<br>
<div align="center" style="border:1px solid black; font-family:verdana;">
<h3 style="color:blue"> 회원가입 인증 코드 입니다. </h3>
<div style="font-size:130%" th:text="${code}"> </div>
</div>
<br/>
</div>
</body>
</html>
위치: resources/templates/password.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div style="margin:100px;">
<h1> 안녕하세요.</h1>
<h1> 개발자를 위한 면접 준비 플랫폼 SAVIEW 입니다.</h1>
<br>
<p> 임시 비밀번호를 발급드립니다. 아래 발급된 비밀번호로 로그인해주세요. </p>
<br>
<div align="center" style="border:1px solid black; font-family:verdana;">
<h3 style="color:blue"> 임시 비밀번호 입니다. </h3>
<div style="font-size:130%" th:text="${code}"> </div>
</div>
<br/>
</div>
</body>
</html>
Spring를 이용한 이메일 인증(feat. 네이버,구글)
gmail 설정에서 pop 과 imap 프로토콜을 같이 사용하게 되면 메일 저장방식에 있어서 문제가 발생할 수 있을 거 같은데 둘다 활성화 해서 사용하는 이유가 있을까요?