Spring

[Spring] Email 인증 시스템 구현

lakelight 2022. 12. 22. 10:37
728x90
반응형
간단한 Email 인증 시스템을 만들어보겠습니다.

 

동작 순서

  1. 사용자가 이메일을 입력
  2. 랜덤 코드를 생성
  3. Email 전송 라이브러리를 이용하여 코드를 사용자 Email에 전송
  4. 사용자가 인증 코드를 입력하면 인증 성공 (시스템 내부에서 맞는지 체크)

 

Dependencies 추가 [참고로 스프링 3.0 기준입니다.]

dependencies {
    // 이메일 인증 관련 의존성
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '2.6.3'
    implementation group: 'com.sun.mail', name: 'jakarta.mail', version: '2.0.1'
    implementation group: 'com.sun.activation', name: 'jakarta.activation', version: '2.0.1'
    
    // html 렌더링을 위한 템플릿
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

}

 

디렉터리 구조

공통으로 사용하는 global 패키지 내부에 email 패키지를 생성하여 구현하였습니다.

 

이메일 인증에 사용할 이메일 설정

저는 구글 이메일을 이용해서 인증 시스템을 구현해보겠습니다.

구글 계정 설정 → 보안 → 2단계 인증을 사용으로 변경합니다.

앱 선택은 '메일'로 해주시고, 기기 선택은 'Windows 컴퓨터'로 하고 생성을 클릭합니다.

다음과 같이 16자리 비밀번호가 생성되었다면 16자리 비밀번호를 메모장에 저장해둡니다.

다음은 Gmail 앱 설정 탭에서 다음과 같이 설정해줍니다.

 

구현

application.properties

#Email Auth Setting
spring.mail.host=smtp.gmail.com #네이버로 할 시 변경 smtp.naver.com
spring.mail.port=587
spring.mail.username={사용자 Email}
spring.mail.password={발급받은 비밀번호 16자리}
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.auth=true

 

EmailService.java

import hooyn.base.global.exception.CustomException;
import hooyn.base.global.exception.ErrorCode;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.util.Random;

@Service
@RequiredArgsConstructor
public class EmailService {

    private final JavaMailSender emailSender;
    private final TemplateEngine templateEngine;

    //application.properties에서 사용자 Email 정보 가져오기
    @Value("${spring.mail.username}")
    private String from;

    //이메일 전송이 오래걸려서 비동기로 처리하기 위해 @Async 키워드를 사용하여 처리하였습니다.
    //다음과 같이 사용하기 위해서는 Application.class 에 @EnableAsync 을 추가해주어야 합니다.
    @Async
    public void sendAuthEmail(String email, String authCode) {
        try {
            MimeMessage emailForm = makeEmailForm(email, authCode);
            emailSender.send(emailForm);
        } catch (Exception e) {
            throw new CustomException(ErrorCode.BAD_REQUEST, e.getMessage());
        }
    }

    //이메일 인증을 위해 사용자에게 보낼 이메일 폼을 생성합니다.
    private MimeMessage makeEmailForm(String email, String authCode) throws MessagingException {
        MimeMessage message = emailSender.createMimeMessage();

        message.addRecipients(MimeMessage.RecipientType.TO, email); //보낼 사람 설정
        message.setSubject("LakeLight 회원가입 이메일 인증"); //이메일 제목
        message.setFrom(from); //보내는 사람 설정
        message.setContent(setContext(authCode), "text/html;charset=euc-kr"); //setContext를 통해 html을 만들고 전송

        return message;
    }

    //HTML 제작하고 인증 코드도 HTML에서 보여줄 수 있도록 전달
    private String setContext(String authCode) {
        Context context = new Context();
        context.setVariable("code", authCode);
        return templateEngine.process("email", context);
    }

    //인증 코드 제작하는 코드
    //비동기 처리를 위해 public으로 하고 Controller 부분에서 파라미터로 전달
    public String makeAuthCode() {
        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) (random.nextInt(26) + 97));
                case 1 -> key.append((char) (random.nextInt(26) + 65));
                case 2 -> key.append(random.nextInt(9));
            }
        }
        return key.toString();
    }
}

 

EmailContorller.java

import hooyn.base.global.response.ResponseWrapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
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 java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/email/auth")
public class EmailController {

    private final HttpServletRequest request;
    private final EmailService emailService;

    @PostMapping("")
    public ResponseEntity<?> confirmEmailAuth(@RequestBody ConfirmEmailAuthRequestDto dto) {

        //빠른 응답을 하고 로직은 비동기처리를 하기 위해 인증 코드를 생성하고,
        String authCode = emailService.makeAuthCode();
        //비동기로 처리하기 위해 인증 코드를 전달한다.
        emailService.sendAuthEmail(dto.getEmail(), authCode);

        return new ResponseEntity<>(new ResponseWrapper(request, HttpStatus.OK,
                true, "이메일이 성공적으로 전송되었습니다.", "AuthCode: [" + authCode + "]" ), HttpStatus.OK);
    }
}

 

ConfirmEmailAuthRequestDto.java

import lombok.Getter;

@Getter
public class ConfirmEmailAuthRequestDto {

    private String email;
}

 

index.html 위치와 코드

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div style="margin:100px;">
  <h1> 안녕하세요.</h1>
  <h1> LakeLight Company 입니다.</h1>
  <br>
  <p> 아래 코드를 회원가입 창으로 돌아가 입력해주세요.</p>
  <br>

  <div style="font-family:verdana;">
    <h3 style="color:blue"> 회원가입 인증 코드 입니다. </h3>
    <div>
      <div th:text="${code}"></div>
    </div>
  </div>
  <br/>
</div>


</body>
</html>

 

Postman으로 API 호출 후 결과 화면

비동기로 처리하기 때문에 355ms 만 걸리는 것을 확인할 수 있습니다. 비동기로 처리하기 전에는 로컬 PC 기준 3초가 걸렸는데, 비동기로 처리하고 355ms 로 줄어들었습니다.

 

파라미터로 넘긴 email로 Email 온 화면

 

마무리

오늘은 늘 생각만으로 하던 이메일 인증에 대해서 구현해보았습니다.

라이브러리로 잘 구현되어 있어서
생각했던 것보다는 어려운 점이 없었던 것 같습니다.

다음 프로젝트를 할 때는 간단한게 적용해서 사용할 수 있을 것 같습니다.

또한 프로젝트에서 사용자에게 이메일을 보내야할 때 빠르게 구현할 수 있을 것 같습니다.

 

[참고]

1. Spring을 이용한 이메일 인증

2. [Spring] @Async 사용 방법

728x90
반응형