Spring Boot | 메일 발송 기능 구현하기 (Gmail)

yeonk·2022년 10월 7일
12

spring & spring boot

목록 보기
2/10
post-thumbnail

목적 및 개요


프로젝트를 진행하던 중에 회원에게 메일을 발송하는 기능을 구현해야겠다는 생각이 들었다.
회원가입 시 이메일 인증을 하고, 비밀번호를 분실했을 때 임시 비밀번호를 발송하는 기능을 구현하였다.

메일을 발송하는 방법은 여러가지가 있지만 Gmail의 SMTP 서버를 활용하여 이메일을 발송하는 방법을 이용하여 기능을 구현해보았다.





개발 환경

  • Spring Boot 2.7.3

  • Java 11

  • IntelliJ










구글 계정 설정


Gmail의 SMTP 서버를 활용하기 위한 절차가 필요하다.

  1. 구글 계정 관리에 들어가서 보안 탭을 클릭한다.

  2. 2단계 인증을 설정하지 않았다면 사용하는 것으로 설정을 변경한다.

  3. 아래 이미지를 참고하여 앱 비밀번호를 설정한다.





  1. 앱 선택 목록에서 메일을 선택한다.

  2. 기기 선택 목록에서 기타를 선택한 후 기기 이름을 입력한다(원하는대로 작성).

  3. 생성 버튼을 눌러 앱 비밀번호를 생성한다.





  1. 16자리의 앱 비밀번호를 확인합니다. 앱 비밀번호는 노출되지 않도록 주의하고, 개인 공간에 복사해둔다.

  2. 확인버튼을 눌러 완료한다.










구글 메일 설정


  1. Gmail 설정에 들어가서 전달 및 POP/IMAP 탭으로 들어간다.

  2. 모든 메일에 POP를 활성화 하기를 선택한다.

  3. IMAP 사용을 선택한다.

  4. 변경사항을 저장한다.










의존성 & yml 파일 설정


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
  1. SMTP 서버 호스트

  2. SMTP 서버 포트

  3. SMTP 서버 로그인 아이디: 발신자 (이메일이 test@gmail.com 이면 test가 해당)

  4. SMTP 서버 로그인 패스워드: 앱 비밀번호

  5. 사용자 인증 시도 여부 (기본값 : false)

  6. Socket Read Timeout 시간(ms) (기본값 : 무한대)

  7. StartTLS 활성화 여부 (기본값 : false)





인텔리제이 환경변수 설정

메일 정보와 앱 비밀번호가 유출되는 것을 대비하기 위해 application.yml에서 username과 password를 직접적으로 쓰지 않고 mail.username, mail.password 와 같이 작성하여 환경변수를 설정해준다.

  1. Edit Configurations에 들어간다.

  2. 이미지를 참고하여 환경변수를 설정해준다.
    (이미지는 이메일이 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 










코드 작성

이메일 인증 코드와 임시 비밀번호를 동일한 메서드를 이용하여 발급하였다.

EmailMessage.java

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: 메일 내용





EmailPostDto.java

package server.email.dto;

import lombok.Getter;

@Getter
public class EmailPostDto {
    private String email;
}
  • 굳이 DTO를 사용할 필요는 없으며, 필요하다고 느껴진다면 작성





EmailResponseDto.java

package server.email.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class EmailResponseDto {
    private String code;
}
  • 굳이 DTO를 사용할 필요는 없으며, 필요하다고 느껴진다면 작성





EmailService.java

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 값을 받도록 하였다.





EmailController.java

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 를 적용할 경우에만 참고하면 된다.




email.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>





password.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. 네이버,구글)

[Spring] SMTP 서버를 이용한 이메일 발송

Class MimeMessage

[Spring/Java] 회원가입 이메일 인증 구현하기

[Spring Boot] 이메일 인증 회원가입하기 구현

1개의 댓글

comment-user-thumbnail
2023년 9월 28일

gmail 설정에서 pop 과 imap 프로토콜을 같이 사용하게 되면 메일 저장방식에 있어서 문제가 발생할 수 있을 거 같은데 둘다 활성화 해서 사용하는 이유가 있을까요?

답글 달기