본문 바로가기

Language/Spring

[Spring Boot] 구글 이메일 인증 코드 발급 구현

구글 SMTP 서비스 설정

1. 자신의 Gmail로 접속 → 우측 상단 톱니 바퀴 클릭 → 모든 설정 보기 클릭

2. 아래와 같이 IMAP 사용 설정

변경 사항 저장을 눌렀을 때 본인 인증 창이 활성화 되긴 하였음

3. 우측 상단 구글 계정을 클릭 구글 계정 관리 클릭

4. 보안 →  Google에 로그인하는 방법 → 2단계 인증 설정

설정이 되었다면 넘어가도 됨

5. 상단 검색창에 '앱 비밀번호' 검색하여 앱 이름 입력 후 만들기 → 생성된 비밀번호는 외부에 유출되지 않도록 저장


build.gradle 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-mail'

 

application.yml 설정 추가

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: 보낼 이메일(From)
    password: 앱비밀번호(16자리)
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
          connectiontimeout: 5000
          timeout: 5000
          writetimeout: 5000
      auth-code-expiration-millis: 300000  # 5 * 60 * 1000(밀리초) == 5분
  • host
    → Gmail의 SMTP 서버 호스트
  • port
    SMTP 서버의 포트 번호 (Gmail SMTP 서버는 587번 포트)
  • username 
    → 이메일을 보내는 용으로 사용되는 계정의 이메일 주소 입력
  • password 
    → 위에서 생성했던 앱 비밀번호 입력
  • properties
    → 이메일 구성에 대한 추가 속성
  • auth
    → SMTP 서버에 인증 필요한 경우 true로 지정
    Gmail SMTP 서버는 인증을 요구하기 때문에 true 설정
  • starttls
    → SMTP 서버가 TLS를 사용하여 안전한 연결을 요구하는 경우 true로 설정
    TLS는 데이터를 암호화하여 안전한 전송을 보장하는 프로토콜
  • connectiontimeout
    → 클라이언트가 SMTP 서버와의 연결을 설정하는 데 대기해야 하는 시간(Millisecond)
    연결이 불안정한 경우 대기 시간이 길어질 수 있기 때문에 너무 크게 설정하면 전송 속도가 느려질 수 있음
  • timeout
    → 클라이언트가 SMTP 서버로부터 응답을 대기해야 하는 시간(Millisecond)
    서버에서 응답이 오지 않는 경우 대기 시간을 제한하기 위해 사용
  • writetimeout
    → 클라이언트가 작업을 완료하는데 대기해야 하는 시간(Millisecond)
    이메일을 SMTP 서버로 전송하는 데 걸리는 시간을 제한하는데 사용
  • auth-code-expiration-millis
    → 이메일 인증 코드의 만료 시간(Millisecond)

MailConfig

  • @Value 어노테이션을 이용하여 applicaiton.yml에서 설정한 환경 변수 값들을 가지고 와서 변수에 넣어줌
  • JavaMailSenderImpl에 값들을 설정한 뒤 JavaMailSender 인터페이스로 객체 등록
@Configuration
public class MailConfig {
    @Value("${spring.mail.host}")
    private String host;

    @Value("${spring.mail.port}")
    private int port;

    @Value("${spring.mail.username}")
    private String username;

    @Value("${spring.mail.password}")
    private String password;

    @Value("${spring.mail.properties.mail.smtp.auth}")
    private boolean auth;

    @Value("${spring.mail.properties.mail.smtp.starttls.enable}")
    private boolean starttlsEnable;

    @Value("${spring.mail.properties.mail.smtp.starttls.required}")
    private boolean starttlsRequired;

    @Value("${spring.mail.properties.mail.smtp.connectiontimeout}")
    private int connectionTimeout;

    @Value("${spring.mail.properties.mail.smtp.timeout}")
    private int timeout;

    @Value("${spring.mail.properties.mail.smtp.writetimeout}")
    private int writeTimeout;

    @Bean
    public JavaMailSender javaMailSender() {
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost(host);
        mailSender.setPort(port);
        mailSender.setUsername(username);
        mailSender.setPassword(password);
        mailSender.setDefaultEncoding("UTF-8");
        mailSender.setJavaMailProperties(getMailProperties());

        return mailSender;
    }

    private Properties getMailProperties() {
        Properties properties = new Properties();
        properties.put("mail.smtp.auth", auth);
        properties.put("mail.smtp.starttls.enable", starttlsEnable);
        properties.put("mail.smtp.starttls.required", starttlsRequired);
        properties.put("mail.smtp.connectiontimeout", connectionTimeout);
        properties.put("mail.smtp.timeout", timeout);
        properties.put("mail.smtp.writetimeout", writeTimeout);

        return properties;
    }
}

 

MailService

  • senderEmail에는 환경 변수로 설정한 보낼 이메일(username)을 저장
    spring.mail.username에 설정한 email 주소가 보낸 사람(From)이 됨
  • createCode() 메서드를 이용하여 인증 코드 6자리 생성
  • createMail() 메서드를 이용하여 이메일의 제목, 내용, 보낸사람 등을 설정
@Service
@RequiredArgsConstructor
public class MailService {
    private final JavaMailSender javaMailSender;

    @Value("{spring.mail.username}")
    private static String senderEmail;

    public String createCode() {
        Random random = new Random();
        StringBuilder key = new StringBuilder();

        for (int i = 0; i < 6; i++) { // 인증 코드 6자리
            int index = random.nextInt(2); // 0~1까지 랜덤, 랜덤값으로 switch문 실행

            switch (index) {
                case 0 -> key.append((char) (random.nextInt(26) + 65)); // 대문자
                case 1 -> key.append(random.nextInt(10)); // 숫자
            }
        }
        return key.toString();
    }

    public MimeMessage createMail(String mail, String authCode) throws MessagingException {
        MimeMessage message = javaMailSender.createMimeMessage();

        message.setFrom(senderEmail);
        message.setRecipients(MimeMessage.RecipientType.TO, mail);
        message.setSubject("이메일 인증");
        String body = "";
        body += "<h3>요청하신 인증 번호입니다.</h3>";
        body += "<h1>" + authCode + "</h1>";
        body += "<h3>감사합니다.</h3>";
        message.setText(body, "UTF-8", "html");

        return message;
    }

    // 메일 발송
    public boolean sendSimpleMessage(String sendEmail) throws MessagingException {
        String authCode = createCode(); // 랜덤 인증번호 생성

        MimeMessage message = createMail(sendEmail, authCode); // 메일 생성
        try {
            javaMailSender.send(message); // 메일 발송
            return true;
        } catch (MailException e) {
            return false;
        }
    }
}

 

MemberController 코드

@GetMapping("/email/auth/{email}")
public ResponseEntity<String> requestAuthcode(@PathVariable String email) throws MessagingException {
    boolean isSend = mailService.sendSimpleMessage(email);
    return isSend ? ResponseEntity.status(HttpStatus.BAD_REQUEST).body("인증 코드가 전송되었습니다.") :
            ResponseEntity.status(HttpStatus.OK).body("인증 코드 발급에 실패하였습니다.");
}

Postman을 이용하여 인증 코드 발급 확인