Springboot 이메일 인증 구현

 

Summry

본 문서에서는 인증번호를 생성해 이메일을 보내는 방법을 정리한다.

  • 본 문서에서 다룬 방법은 Gmail을 기준으로 작성됨.

send me email if you have any questions.


구글 계정 설정

2022년 5월 기준 보안 수준이 낮은 앱의 액세스 설정이 불가능하게 되었다. 따라서 아래의 방법을 따라 앱 비밀번호를 생성해야 정상적인 메일 발송이 가능하다.

1. 구글에 로그인 한 후 오른쪽 위 동그란 프로필을 눌러서 Google 계정 관리 접속

그림1

2. 왼쪽의 보안 탭으로 들어가서 2단계 인증을 활성화한다.
3. 2단계 인증을 받았다면 앱 비밀번호를 클릭한다.

그림2

4. 메일, windows 컴퓨터를 선택한다.

그림3

5. 발급받은 앱 비밀번호를 사용한다.
(아래 그림의 앱 비밀번호는 포스팅용으로 발급하였으며 현재는 제거되었음.)

그림4

Gmail 이메일 발송 한도

보통 수신자가 수백명을 넘어가는 대량 발송 이메일은 대량 이메일 발송 서비스나 마케팅 자동화 솔루션을 활용해 발송 및 관리하는 것이 가장 효율적이다. 하지만 이런 전문 솔루션을 활용하기 위해서는 추가 비용이 들기 때문에 비용 절감을 위해 대부분의 소규모 기업들은 웹메일을 통한 대량 이메일 발송을 고려할 수 밖에 없다.

Gmail의 개인 계정의 경우 일일 전송한도는 하루(24시간)에 500개로 제한되어 있다.
Google에서는 각 이메일 주소를 각기 다른 이메일로 계산하고 있기 때문에 5명의 수신자에게 보낸 하나의 이메일을 5개의 이메일로 계산한다.

  • 1회 전송 한도: 100개 (메일 당 최대 100명에게 이메일을 전송할 수 있다.)
  • 일일 (24시간) 전송 한도: 500개
  • 메일 당 수신자: 외부 500개

Google Workspace (전 G Suite)를 활용하는 경우의 일일 전송한도는 하루(24시간)에 2,000개이다. 더불어 Google의 SMTP 서비스를 사용하는 경우 메일 당 최대 100명의 수신자에게 이메일을 전송할 수 있다.

타 메일 발송 한도

네이버

  • 시간 한도: 시간당 최대 30회
  • 1회 전송 한도: 100개

다음 / 다음스마트워크

  • 1회 전송 한도: 150개
  • 일일 (24시간) 전송 한도: 10,000개

1. Dependency 추가

build.gradle

dependencies {
     implementation 'org.springframework.boot:spring-boot-starter-mail'
}

2. EmailConfig

import java.util.Properties;
 
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;
 
 
@Configuration
@PropertySource("classpath:email.properties")
public class EmailConfig {
 
    @Value("${mail.smtp.port}")
    private int port;
    @Value("${mail.smtp.socketFactory.port}")
    private int socketPort;
    @Value("${mail.smtp.auth}")
    private boolean auth;
    @Value("${mail.smtp.starttls.enable}")
    private boolean starttls;
    @Value("${mail.smtp.starttls.required}")
    private boolean startlls_required;
    @Value("${mail.smtp.socketFactory.fallback}")
    private boolean fallback;
    @Value("${AdminMail.id}")
    private String id;
    @Value("${AdminMail.password}")
    private String password;
 
    @Bean
    public JavaMailSender javaMailService() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
        javaMailSender.setHost("smtp.gmail.com");
        javaMailSender.setUsername(id);
        javaMailSender.setPassword(password);
        javaMailSender.setPort(port);
        javaMailSender.setJavaMailProperties(getMailProperties());
        javaMailSender.setDefaultEncoding("UTF-8");
        return javaMailSender;
    }
    private Properties getMailProperties()
    {
        Properties pt = new Properties();
        pt.put("mail.smtp.socketFactory.port", socketPort);
        pt.put("mail.smtp.auth", auth);
        pt.put("mail.smtp.starttls.enable", starttls);
        pt.put("mail.smtp.starttls.required", startlls_required);
        pt.put("mail.smtp.socketFactory.fallback",fallback);
        pt.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        return pt;
    }
}

3. properties

mail.smtp.auth=true
mail.smtp.starttls.required=true
mail.smtp.starttls.enable=true
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.fallback=false
mail.smtp.port=465
mail.smtp.socketFactory.port=465
 
# admin 구글 계정
AdminMail.id = 구글 아이디 입력
AdminMail.password = 발급받은 앱 비밀번호를 입력

4. EmailService

public interface EmailService {
    String sendSimpleMessage(String to)throws Exception;
}

5. EmailServiceImpl

Service와 ServiceImpl을 분리하는 이유

import java.util.Random;
 
import javax.mail.Message.RecipientType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
 
@Service
public class EmailServiceImpl implements EmailService{
 
    @Autowired
    JavaMailSender emailSender;
 
    public static final String ePw = createKey();
 
    private MimeMessage createMessage(String to)throws Exception{
        System.out.println("보내는 대상 : "+ to);
        System.out.println("인증 번호 : "+ePw);
        MimeMessage  message = emailSender.createMimeMessage();
 
        message.addRecipients(RecipientType.TO, to);//보내는 대상
        message.setSubject("이메일 인증 테스트");//제목
 
        String msgg="";
        msgg+= "<div style='margin:20px;'>";
        msgg+= "<h1> 안녕하세요 임준호입니다. </h1>";
        msgg+= "<br>";
        msgg+= "<p>아래 코드를 복사해 입력해주세요<p>";
        msgg+= "<br>";
        msgg+= "<p>감사합니다.<p>";
        msgg+= "<br>";
        msgg+= "<div align='center' style='border:1px solid black; font-family:verdana';>";
        msgg+= "<h3 style='color:blue;'>회원가입 인증 코드입니다.</h3>";
        msgg+= "<div style='font-size:130%'>";
        msgg+= "CODE : <strong>";
        msgg+= ePw+"</strong><div><br/> ";
        msgg+= "</div>";
        message.setText(msgg, "utf-8", "html");//내용
        message.setFrom(new InternetAddress("properties에 입력한 이메일","limjunho"));//보내는 사람
 
        return message;
    }
 
    public static String createKey() {
        StringBuffer key = new StringBuffer();
        Random rnd = new Random();
 
        for (int i = 0; i < 8; i++) { // 인증코드 8자리
            int index = rnd.nextInt(3); // 0~2 까지 랜덤
 
            switch (index) {
                case 0:
                    key.append((char) ((int) (rnd.nextInt(26)) + 97));
                    //  a~z  (ex. 1+97=98 => (char)98 = 'b')
                    break;
                case 1:
                    key.append((char) ((int) (rnd.nextInt(26)) + 65));
                    //  A~Z
                    break;
                case 2:
                    key.append((rnd.nextInt(10)));
                    // 0~9
                    break;
            }
        }
        return key.toString();
    }
    @Override
    public String sendSimpleMessage(String to)throws Exception {
        // TODO Auto-generated method stub
        MimeMessage message = createMessage(to);
        try{//예외처리
            emailSender.send(message);
        }catch(MailException es){
            es.printStackTrace();
            throw new IllegalArgumentException();
        }
        return ePw;
    }
}

6. Controller

@PostMapping("/emailConfirm")
public String emailConfirm(@RequestParam String email) throws Exception {

  String confirm = emailService.sendSimpleMessage(email);

  return confirm;
}

7. 메일 확인

  • query parameter로 인증번호를 받을 이메일 입력.
  • front에 반환한 인증 코드와 서버 터미널에 찍힌 인증 코드가 같은지 확인.

그림5

PC 메일 확인

그림6

모바일 메일 확인

그림7

Reference

[Spring Boot] 이메일 인증 회원가입하기 구현 - 먹여살릴꽁이가있습니다.
Java/SpringBoot 메일을 보내는 방법들 - 코딩하는보기
이메일 대량 발송에 대한 모든 것! - 아웃룩 토탈 애드인 솔루션, 토틀 (totle)
메일 주고받기 한도 - Gmail 고객센터