공부메모 & 오류해결/Spring Boot

[Spring Boot + SMTP] 이메일 인증 구현

남건욱 2024. 4. 1. 10:47
반응형

1. 개발환경

Java 17

Spring Boot 3.1.8

Gradle 8.5

 

 

 

 

 

2. 의존성 추가

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

    // Redis
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'

SMTP와 Redis의 의존성을 build.gradle에 추가해 줍니다.

 

 

3. Gmail SMTP 설정

 

3-1

구글로그인 후 지메일로 들어갑니다.

 

 

3-2

오른쪽 상단의 톱니바퀴 버튼을 누른 뒤 <모든 설정 보기> 버튼을 클릭합니다.

 

 

3-3

 

전달 및 POP/IMAP를 눌러주신 후 사진과 같이 체크한 뒤 <변경사항 저장> 버튼을 눌러 저장해 줍니다.

 

 

3-4

이제 우측 상단의 프로필버튼을 눌러서 <Google 계정 관리>를 클릭해 줍니다.

 

 

 

3-5

이 화면이 뜰 텐데 여기서 보안을 클릭해 줍니다

 

 

 

3-6

스크롤을 내린 뒤 Google에 로그인하는 방법 탭에서 <2단계 인증>을 클릭해 줍니다.

 

 

 

3-7

스크롤을 제일 밑으로 내리면 앱 비밀번호 탭이 있습니다. 클릭해 줍니다.

 

 

 

3-8

사용할 이름을 적고 만들기를 눌러줍니다

 

 

 

3-9

다음과 같이 비밀번호가 뜰 텐데 추후 application.properties에 작성해야 하니 따로 저장해 둡니다.

 

 

 

 

4. Redis 설치

https://ngwdeveloper.tistory.com/94

 

[Redis] Redis 설치 방법

Redis NoSQL DB의 한 종류이며 우리가 흔히 사용하는 MYSQL, Orcal DB, PostgreSQL 등 RDBMS와 다르게 NoSQL DB이다. 그렇다면 무슨 차이이고 어느 상황에 사용해야 할까? RDBMS와 NoSQL의 차이 RDBMS (관계형 DB) - 데

ngwdeveloper.tistory.com

설치가 안되어있으신 분들은 위 링크에 들어가서 따라 해 주시면 됩니다. 만약 설치가 되어있다면 넘어가셔도 됩니다.

 

 

 

 

 

5. application.properties 작성

# Email Properties
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=지메일주소@gmail.com
spring.mail.password=2차비밀번호
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

SMTP의 properties 작성법입니다. 아까 발급받은 본인의 지메일 주소와 저장해 둔 비밀번호를 입력해 주시면 됩니다.

 

#Redis
spring.data.redis.host = localhost
spring.data.redis.port = 6379

만약 Redis설치를 방금 하시고 설정이 되지 않으신 상태라면 위 코드를 추가로 properties에 넣어줍니다.

 

 

 

 

 

6. RedisConfig 작성

@Getter
@Configuration
@RequiredArgsConstructor
@EnableRedisRepositories
public class RedisConfig {
    @Value("${spring.data.redis.host}")
    private String host;

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

    // 내장 / 외부 Redis 연결
    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        return new LettuceConnectionFactory(host, port);
    }

    /*
     * RedisConnection 에서 넘겨준 byte 값을 직렬화 RedisTemplate 은
     * Redis 데이터를 저장하고 조회하는 기능을 하는 클래스 REdis cli 를 사용해 Redis 데이터를 직접 조회할때,
     * Redis 데이터를 문자열로 반환하기 위한 설정
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate(){
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

 

 

 

 

 

7. RedisUtil 작성

@Service
@RequiredArgsConstructor
public class RedisUtil {

    private final StringRedisTemplate redisTemplate;

    public String getData(String key) {
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
        return valueOperations.get(key);
    }

    public boolean existData(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    public void setDataExpire(String key, String value, long duration) {
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
        Duration expireDuration = Duration.ofSeconds(duration);
        valueOperations.set(key, value, expireDuration);
    }

    public void deleteData(String key) {
        redisTemplate.delete(key);
    }

}

 

 

 

 

8. EmailAuthResponseDto 작성

@Getter
public class EmailAuthResponseDto {
    private boolean success;
    private String responseMessage;

    public EmailAuthResponseDto(boolean success, String responseMessage){
        this.success = success;
        this.responseMessage = responseMessage;
    }
}

응답결과를 담을 DTO입니다. 성공, 실패 여부와 메시지를 담아서 반환합니다.

 

 

 

 

 

9. EmailController 작성

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

    private final EmailService emailService;

    // 인증번호 전송
    @GetMapping("/auth")
    public EmailAuthResponseDto sendAuthCode(@RequestParam String address) {
        return emailService.sendEmail(address);
    }

    // 인증번호 검증
    @PostMapping("/auth")
    public EmailAuthResponseDto checkAuthCode(@RequestParam String address, @RequestParam String authCode) {
        return emailService.validateAuthCode(address, authCode);
    }
}

사용할 컨트롤러입니다. 인증번호를 전송하고 검증하는 두 개의 컨트롤러만 사용합니다. 필요한 게 있다면 추가로 작성하셔서 사용하시면 됩니다.

 

 

 

 

 

10. EmailService 작성

@Service
@RequiredArgsConstructor
public class EmailService {

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

    private final JavaMailSender mailSender;
    private final RedisUtil redisUtil;

    public EmailAuthResponseDto sendEmail(String toEmail) {
        if (redisUtil.existData(toEmail)) {
            redisUtil.deleteData(toEmail);
        }

        try {
            MimeMessage emailForm = createEmailForm(toEmail);
            mailSender.send(emailForm);
            return new EmailAuthResponseDto(true, "인증번호가 메일로 전송되었습니다.");
        } catch (MessagingException | MailSendException e) {
            return new EmailAuthResponseDto(false, "메일 전송 중 오류가 발생하였습니다. 다시 시도해주세요.");
        }
    }

    private MimeMessage createEmailForm(String email) throws MessagingException {

        String authCode = String.valueOf(ThreadLocalRandom.current().nextInt(100000, 1000000));

        MimeMessage message = mailSender.createMimeMessage();
        message.setFrom(senderEmail);
        message.setRecipients(MimeMessage.RecipientType.TO, email);
        message.setSubject("인증코드입니다.");
        message.setText(setContext(authCode), "utf-8", "html");

        redisUtil.setDataExpire(email, authCode, 10 * 60L); // 10분

        return message;
    }

    private String setContext(String authCode) {
        String body = "";
        body += "<h4>" + "인증 코드를 입력하세요." + "</h4>";
        body += "<h2>" + "[" + authCode + "]" + "</h2>";
        return body;
    }

    public EmailAuthResponseDto validateAuthCode(String email, String authCode) {
        String findAuthCode = redisUtil.getData(email);
        if (findAuthCode == null) {
            return new EmailAuthResponseDto(false, "인증번호가 만료되었습니다. 다시 시도해주세요.");
        }

        if (findAuthCode.equals(authCode)) {
            return new EmailAuthResponseDto(true, "인증 성공에 성공했습니다.");

        } else {
            return new EmailAuthResponseDto(false, "인증번호가 일치하지 않습니다.");
        }
    }
}

서비스 코드입니다. message에 담을 내용을 set해준 뒤 반환하는 메서드, 인증코드를 입력하도록 바디에 넣어서 반환하는 메서드, 인증번호를 검증하는 메서드로 이루어져 있습니다.

 

 

 

 

 

11. 테스트

인증번호를 수신할 이메일을 address 값으로 지정해 준 뒤 GET 해주면 정상적으로 보내졌다는 true와 메시지, 200 코드가 반환됩니다.

 

 

메시지함에 들어가도 정상적으로 수신된 걸 확인할 수 있습니다.

 

 

RequestParam방식으로 address에는 인증할 이메일, authCode에 인증번호를 넣고 POST 해본 결과 정상적으로 인증에 성공된 것을 확인할 수 있습니다.

반응형