Spring를 이용한 이메일 인증(feat. 네이버,구글)

구본식·2022년 9월 11일
10

1. 시작하기에 앞서

프로젝트에서 일반 회원가입에서 회원의 인증을 위해서 이메일 인증 기능을 넣게 되었다. 기능을 구현하면서 진행했던 절차들을 정리해보도록 하겠다.

google SMTP 서버, Naver SMTP 서버등 다양한 서버를 사용하여 메일을 전송할수 있는데 이번 프로젝트에서는 Naver SMTP 서버를 이용하여 메일 인증을 진행하였다. 하지만 Google SMTP 서버도 설정하여 사용해보았다. 절차나 단계는 거의 유사하다고 볼수 있다.

또한 스프링에서 기본적으로 제공해주는 API를 사용하여 어렵지 않게 구현할수 있었다.

❗ 나중에 정리할것
1. SMTP 프로토콜에 대해서
2. JavaMailSander는 동기? 비동기? 처리

2. SMTP용 계정 설정

추후 Spring에서 해당 SMTP 서버를 사용하기 전에 해당 Naver SMTP 서버를 활성화 시켜준다. 네이버 메일서버를 외부에서 사용하기 위해서는 pop3/SMTP 설정을 필수로 활성화 시켜주어야된다.
아이디, 패스워드, SMTP 포트, SSL등은 yml파일 설정에서 사용할 예정이다.

2.1 Naver


2.2 Google

2.2.1 앱 비밀번호는 2단계 인증을 해야 생성된다.

2.2.2 메일, Window 컴퓨터용 앱 비밀번호 생성

2.2.3 앱 비밀번호 저장(smtp 설정에서 사용될 비밀번호)

2.2.4

3. 의존성 및 yml 파일 설정

3.1 build.gradle

  • Spring에서 메일서버와 연결해서 메일을 보내는 작업을 하는데 필요한 라이브러리를 추가해준다.
//spring email
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '2.6.3'

3.2 application.yml

  • Spring 에서 메일 서버(naver)를 사용하기 위해서는 메일 서버와 Spring을 연결해야된다. Spring 자체로 메일을 보내고 받는 것이아니라 SMTP 메일 서버를 사용하기 때문에 Spring안에서 연결하고자 하는 메일 서버의 정보를 필수로 알아야 된다.
  # Naver smtp server 사용
spring:  
  mail:
    host: smtp.naver.com #smtp 서버 주소
    port: 465 # 메일 인증서버 포트
    username: xxxx #네이버 아이디
    password: xxxx #네이버 비밀번호
    properties:
      mail:
        smtp:
          starttls:
            enable: true
#            required: true
          auth: true
#          connectiontimeout: 5000
#          timeout: 5000
#          writetimeout: 5000
          ssl:
            #trust: smtp.naver.com
            enable: true #반드시 필요 -> SSL 필요로 되어있으면 

Naver SMTP 계정 설정에 있는 값들로 설정한다.


Google

spring:
  mail:
    # Google smtp server 사용
    host: smtp.gmail.com
    port: 587
    username: xxxx #앱 비밀번호 발급받은 google 계정
    password: xxxx #발급받은 앱 비밀번호
    properties:
      mail:
        smtp:
          starttls:
            enable: true
          auth: true

4. MailService 구현 (tymeleaf 이용)

MailService안에는 총 3가지 기능의 메서드가 존재한다.
1. createCode: 인증번호 8자리를 무작위로 생성하는 기능을 한다.
2. createEmailForm : email 전송에 필요한 정보들을 설정하는 기능을 하게 된다. 여기서 전송하고자하는 message의 context에 전송하고자하는 html파일을 타임리프를 이용하여 html와 java 코드가 분리되도록 구성하였다.
3. sendEmail : 실제 메일을 전송하는 기능

@Service
@RequiredArgsConstructor
public class EmailService {

	//의존성 주입을 통해서 필요한 객체를 가져온다.
    private final JavaMailSender emailSender;
    // 타임리프를사용하기 위한 객체를 의존성 주입으로 가져온다
    private final SpringTemplateEngine templateEngine;
    private String authNum; //랜덤 인증 코드

    //랜덤 인증 코드 생성
    public void createCode() {
        Random random = new Random();
        StringBuffer key = new StringBuffer();

        for(int i=0;i<8;i++) {
            int index = random.nextInt(3);

            switch (index) {
                case 0 :
                    key.append((char) ((int)random.nextInt(26) + 97));
                    break;
                case 1:
                    key.append((char) ((int)random.nextInt(26) + 65));
                    break;
                case 2:
                    key.append(random.nextInt(9));
                    break;
            }
        }
        authNum = key.toString();
    }

    //메일 양식 작성
    public MimeMessage createEmailForm(String email) throws MessagingException, UnsupportedEncodingException {

        createCode(); //인증 코드 생성
        String setFrom = "rnqhstlr2297@naver.com"; //email-config에 설정한 자신의 이메일 주소(보내는 사람)
        String toEmail = email; //받는 사람
        String title = "CODEBOX 회원가입 인증 번호"; //제목

        MimeMessage message = emailSender.createMimeMessage();
        message.addRecipients(MimeMessage.RecipientType.TO, email); //보낼 이메일 설정
        message.setSubject(title); //제목 설정
        message.setFrom(setFrom); //보내는 이메일
        message.setText(setContext(authNum), "utf-8", "html");

        return message;
    }

    //실제 메일 전송
    public String sendEmail(String toEmail) throws MessagingException, UnsupportedEncodingException {
    
        //메일전송에 필요한 정보 설정
        MimeMessage emailForm = createEmailForm(toEmail);
        //실제 메일 전송
        emailSender.send(emailForm);

        return authNum; //인증 코드 반환
    }

    //타임리프를 이용한 context 설정
    public String setContext(String code) {
        Context context = new Context();
        context.setVariable("code", code);
        return templateEngine.process("mail", context); //mail.html
    }

}

※ mail html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<body>
<div style="margin:100px;">
    <h1> 안녕하세요.</h1>
    <h1> 알고리즘 관리 서비스 CODEBOX 입니다.</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>

5. test Controller 구현

@RestController
@RequiredArgsConstructor
public class EmailController {

    private final EmailService emailService;

    @PostMapping("login/mailConfirm")
    public String mailConfirm(@RequestBody EmailAuthRequestDto emailDto) throws MessagingException, UnsupportedEncodingException {

        String authCode = emailService.sendEmail(emailDto.getEmail());
        return authCode;
    }
}
@Data
public class EmailAuthRequestDto {

    @NotEmpty(message = "이메일을 입력해주세요")
    public String email;
}

6. test

  • 이메일(Request) 요청 -> 응답으로 랜덤 인증코드 반환
  • 이메일 확인
profile
백엔드 개발자를 꿈꾸며 기록중💻

4개의 댓글

comment-user-thumbnail
2022년 12월 15일

혹시 EmailService 클래스에서 import는 어떤 것을 해야하나요?
도움 요청 드립니다~^^

답글 달기
comment-user-thumbnail
2023년 1월 6일

큰 도움 되었습니다! 감사합니다!

답글 달기
comment-user-thumbnail
2023년 7월 6일

잘 보고 갑니다.

답글 달기
comment-user-thumbnail
2023년 10월 27일

같은 환경은 아니었지만 프로젝트의 버전 및 패턴을 맞게 적용하여 구현에 성공했습니다. 정말 감사합니다!

답글 달기