[Spring Boot] 회원가입, 비밀번호 재설정 시 이메일로 인증 구현(SMTP)

2023. 5. 23. 01:56Backend/Spring

 

예전부터 프로젝트에 회원가입/비밀번호 재설정 시 이메일로 인증 받는 기능을 구현해보고 싶었는데, 드디어 구현해보게 됐다.

그 과정을 정리해보려 한다.

 

 

 

환경설정1 - Gradle에  의존성 추가

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

Spring Boot Starter Mail 라이브러리를 임포트해준다.

 

 

 

환경설정2 - 네이버 메일 설정

'네이버 메일 > 환경설정> POP3/IMAP 설정' 으로 들어간다.

SMTP 서버명, SMTP 포트, 아이디 확인 후 아래 사진과 같이 환경 설정을 마친다.

 

 

 

 

환경설정3 -  application.yml 설정

application.yml 파일에 따로 추가하지 않고 config 파일을 만들수도 있다.

하지만 계정 이메일과 비밀번호를 지키기 위해 application.yml 파일에 설정 정보를 추가해 준 뒤 config 파일들을 관리하는 디렉터리 밑에 mail config 파일을 따로 만들기로 했다.

 

 

application.yml 파일에 설정 정보는 다음과 같이 추가한다.

spring:
  mail:
    host: smtp.naver.com
    port: 465
    username: 아이디@naver.com
    password: 비밀번호
    properties:
      mail.smtp.auth: true
      mail.smtp.ssl.enable: true
      mail.smtp.ssl.trust: smtp.naver.com
      mail.smtp.starttls.enable: true

 

 

 

 

 

환경설정4 - MailConfig 파일 작성

package com.example.gabojago_server.config;

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;

import java.util.Properties;

@Configuration
@PropertySource("classpath:application.yml")
public class MailConfig {

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

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

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

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

    @Bean
    public JavaMailSender javaMailService(){
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();

        javaMailSender.setHost(host);	//smtp 서버 주소
        javaMailSender.setUsername(id);	//발신 메일 아이디
        javaMailSender.setPassword(password);	//발신 메일 비밀번호
        javaMailSender.setPort(port);	//smtp port
        javaMailSender.setJavaMailProperties(getMailProperties());	//메일 인증서버 정보 가져오기
        javaMailSender.setDefaultEncoding("UTF-8");
        return javaMailSender;
    }

    private Properties getMailProperties(){
        Properties properties = new Properties();
        properties.setProperty("mail.transport.protocol", "smtp");	//프로토콜 설정
        properties.setProperty("mail.smtp.auth", "true");	//smtp 인증
        properties.setProperty("mail.smtp.starttls.enable", "true");	//smtp starttls 사용
        properties.setProperty("mail.debug", "true");	//디버그 사용
        properties.setProperty("mail.smtp.ssl.trust", "smtp.naver.com");	//ssl 인증 서버 주소
        properties.setProperty("mail.smtp.ssl.enable", "true");	//ssl 사용
        return properties;
    }
}

 

 

 

 

 

메일 발송 구현 - Service(이메일 인증, 비밀번호 재설정) 작성

  • createMessage - 인증코드를 포함한 메일 양식(내용) 작성
  • createKey - 랜덤 8자리 인증코드 생성
  • sendSimpleMessgae - 실제 메일 발송
package com.example.gabojago_server.service.mail;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.util.Random;

@PropertySource("classpath:application.yml")
@Slf4j
@RequiredArgsConstructor
@Service
public class EmailService {

    private final JavaMailSender javaMailSender;

    private final String ePw = createKey(); //인증번호 

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

	//메일 양식 작성
    public MimeMessage createMessge(String to) throws MessagingException, UnsupportedEncodingException {
        log.info("보내는 대상 : " + to);
        log.info("인증 번호 : " + ePw);

        MimeMessage message = javaMailSender.createMimeMessage();

        message.addRecipients(MimeMessage.RecipientType.TO, to);    //수신자
        message.setSubject("가보자Go 인증 코드입니다.");   //메일 제목

        String msg = "";
        msg += "<div style='margin:100px;'>";
        msg += "<h1 style=\"font-size: 30px; padding-right: 30px; padding-left: 30px;\">가보자Go 이메일 인증 확인</h1>";
        msg += "<p style=\"font-size: 17px; padding-right: 30px; padding-left: 30px;\">아래 확인 코드를 서비스 화면으로 돌아가 입력해주세요.</p>";
        msg += "<div style=\"padding-right: 30px; padding-left: 30px; margin: 32px 0 40px;\"><table style=\"border-collapse: collapse; border: 0; background-color: #F4F4F4; height: 70px; table-layout: fixed; word-wrap: break-word; border-radius: 6px;\"><tbody><tr><td style=\"text-align: center; vertical-align: middle; font-size: 30px;\">";
        msg += ePw;
        msg += "</td></tr></tbody></table></div>";

        message.setText(msg, "utf-8", "html");	//내용, charset타입, subtype
        message.setFrom(new InternetAddress(id, "gabojago_Admin"));	//발신자 메일주소, 발신자 이름

        return message;
    }


    //인증 코드 8자리 만들기
    public static String createKey(){
        StringBuffer key = new StringBuffer();
        Random random = new Random();

        for(int i = 0; i<8; i++){
            int idx = random.nextInt(3);	//0~2 경우를 랜덤

            switch (idx){
                case 0:
                    key.append((char) ((int) random.nextInt(26) + 97));	//a~z
                    break;
                case 1:
                    key.append((char) ((int) random.nextInt(26) + 65));	//A~Z
                    break;
                case 2:
                    key.append(random.nextInt(10));	//0~9
                    break;
            }
        }
        return key.toString();
    }

    //메일 발송
    public String sendSimpleMessage(String to) throws Exception {
        MimeMessage message = createMessge(to);

        try{
            javaMailSender.send(message);
        }catch (MailException es){
            es.printStackTrace();
            throw new IllegalAccessException();
        }
        return ePw;	//메일로 보냈던 인증 코드를 서버로 반환
    }
}

 

 

아래는 임시 비밀번호를 send하는 ChangePwEmailService

package com.example.gabojago_server.service.mail;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.util.Random;

@Slf4j
@Service
@RequiredArgsConstructor
public class ChangePwEmailService {

    private final JavaMailSender emailsender;

    private String ePw = createKey(); //인증번호

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

    private static final String SPECIAL_CHARS = "!@#$%^&*";

    public MimeMessage createMessage(String to) throws MessagingException, UnsupportedEncodingException {
        log.info("보내는 대상 : " + to);
        log.info("인증 번호 : " + ePw);

        MimeMessage message = emailsender.createMimeMessage();

        message.addRecipients(MimeMessage.RecipientType.TO, to);    //수신자
        message.setSubject("가보자Go 임시 비밀번호입니다.");   //메일 제목

        String msg = "";
        msg += "<div style='margin:100px;'>";
        msg += "<h1 style=\"font-size: 30px; padding-right: 30px; padding-left: 30px;\">가보자Go 비밀번호 재설정을 위한 임시 비밃번호입니다.</h1>";
        msg += "<p style=\"font-size: 17px; padding-right: 30px; padding-left: 30px;\">아래 임시 비밀번호로 로그인 후 새로운 비밀번호로 변경 부탁드립니다.</p>";
        msg += "<div style=\"padding-right: 30px; padding-left: 30px; margin: 32px 0 40px;\"><table style=\"border-collapse: collapse; border: 0; background-color: #F4F4F4; height: 70px; table-layout: fixed; word-wrap: break-word; border-radius: 6px;\"><tbody><tr><td style=\"text-align: center; vertical-align: middle; font-size: 30px;\">";
        msg += ePw;
        msg += "</td></tr></tbody></table></div>";

        message.setText(msg, "utf-8", "html");
        message.setFrom(new InternetAddress(id, "gabojago_Admin"));

        return message;
    }


    //인증 코드 만들기
    public static String createKey(){
        StringBuffer key = new StringBuffer();
        Random random = new Random();

        boolean hasUpperCase = false;
        boolean hasLowerCase = false;
        boolean hasNumber = false;
        boolean hasSpecialChar = false;

        while(key.length()<8 || !(hasLowerCase && hasUpperCase && hasNumber && hasSpecialChar)){
            int idx = random.nextInt(4);

            switch (idx){
                case 0:
                    if(!hasLowerCase) {
                        key.append((char) ((int) random.nextInt(26) + 97));
                        hasLowerCase = true;
                    }
                    break;
                case 1:
                    if(!hasUpperCase) {
                        key.append((char) ((int) random.nextInt(26) + 65));
                        hasUpperCase = true;
                    }
                    break;
                case 2:
                    if(!hasNumber) {
                        key.append(random.nextInt(10));
                        hasNumber = true;
                    }
                    break;
                case 3:
                    if(!hasSpecialChar) {
                        int charIdx = random.nextInt(SPECIAL_CHARS.length());
                        char specialChar = SPECIAL_CHARS.charAt(charIdx);
                        key.append(specialChar);
                        hasSpecialChar = true;
                    }
                    break;
            }
        }
        return key.toString();
    }

    //메일 발송
    public String sendSimpleMessage(String to) throws Exception {
        MimeMessage message = createMessage(to);

        try{
            emailsender.send(message);
        }catch (MailException es){
            es.printStackTrace();
            throw new IllegalAccessException();
        }
        return ePw;
    }

}

 

 

 

 

 

 

메일 발송 구현 - Controller 작성

@PostMapping("/signup/mailConfirm")
@ResponseBody
public ResponseEntity<String> mailConfirm(@RequestBody String email) throws Exception {
    if(authService.findMember(email)) return ResponseEntity.ok(NormalResponse.duplicated().getStatus());
    String code = signupEmailService.sendSimpleMessage(email);
    return ResponseEntity.ok(code);
}

@PostMapping("/login/findPw")
@ResponseBody
public ResponseEntity<NormalResponse> findPw(@RequestBody String email) throws Exception {
    if(authService.findMember(email)){
        String newPassword = pwEmailService.sendSimpleMessage(email);
        authService.changeTempPw(email, newPassword);
        return ResponseEntity.ok(NormalResponse.success());
    }else{
        return ResponseEntity.ok(NormalResponse.fail());
    }
}

 

 

 

 

 

 

참고
https://velog.io/@leesomyoung/SpringBoot-SMTP-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%9D%B8%EC%A6%9D-%EB%84%A4%EC%9D%B4%EB%B2%84
https://velog.io/@kyungwoon/%EC%9D%B4%EB%A9%94%EC%9D%BC%EB%A1%9C-%EC%9D%B8%EC%A6%9D-%EC%BD%94%EB%93%9C-%EB%B3%B4%EB%82%B4%EA%B8%B0SpringBoot-SMTP