Spring + Redis 회원가입이메일 인증 구현

강우엉·2023년 12월 17일
0

study

목록 보기
34/44

💡 요구사항

  • 사용자는 회원가입시 이메일로 인증을한다.
  • 클라이언트는 사용자의 이메일로 서버에게 인증번호를 요청한다.
  • 서버는 랜덤 인증번호를 생성하고 사용자의 이메일에 인증번호를 전송한다.
  • 서버는 전송한 인증번호를 redis에 저장한다.
  • 클라이언트는 서버에게 인증번호 검증을 요청한다.
  • 서버는 전달받은 인증번호가 redis에 저장된 인증번호와 동일한지 검증한다.

💡 SMTP 계정 설정

SMTP를 사용한 구글 계정을 설정해줘야한다.

구글 로그인 → 구글 계정 관리 → 검색창에 “앱 비밀번호” 검색.

만약 앱 비밀번호를 검색해도 나오지 않는다면 2단계 인증을 하지 않았을 확률이 높다. 2단계 인증을 먼저 진행하자. 2단계 인증 과정은 생략한다.

앱 비밀번호를 생성한다.
생성된 비밀번호는 노출시키지않고 저장해둔다.

Gmail -> 라벨 관리 -> 전달 및 POP/IMAP
아래 이미지처럼 설정을 완료한다.

💡 구현

이제부터 구현을 해보자.
build.gradle

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

build.gradle에 이메일과 redis의 의존관계를 설정해줘야한다.

application.yml

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: wooseong1467@gmail.com
    password: "비밀번호"
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
          connectiontimeout: 5000
          timeout: 5000
          writetimeout: 5000
    auth-code-expiration-millis: 1800000

  redis:
    host: localhost
    port: 6379
  • 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)

EmailService
이메일 발송을 담당하는 클래스이다.

package project.stylemate.service;

import lombok.RequiredArgsConstructor;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class EmailService {

    private final JavaMailSender javaMailSender;

    public void sendEmail(String to, String subject, String text) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(to);
        message.setSubject(subject);
        message.setText(text);
        javaMailSender.send(message);
    }
}

VerificationService

package project.stylemate.service;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class VerificationService {

    private final RedisTemplate<String, String> redisTemplate;

    private static final String VERIFICATION_CODE_KEY_PREFIX = "verification-code:";

    public String generateVerificationCode() {
        return String.valueOf((int) (Math.random() * 9000) + 1000);
    }

    public void saveVerificationCode(String email, String verificationCode) {
        String key = VERIFICATION_CODE_KEY_PREFIX + email;
        redisTemplate.opsForValue().set(key, verificationCode, 5, TimeUnit.MINUTES);
    }

    public boolean verifyEmail(String email, String verificationCode) {
        String key = VERIFICATION_CODE_KEY_PREFIX + email;
        String savedCode = redisTemplate.opsForValue().get(key);

        if (savedCode != null && savedCode.equals(verificationCode)) {
            redisTemplate.delete(key);
            return true;
        }
        return false;
    }

}
  • 랜덤 번호 생성
  • RedisTemplateopsForValue().set를 활용하여 인증코드를 redis에 저장
  • opsForValue().get을 활용해 인증번호를 검증

EmailController

package project.stylemate.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import project.stylemate.dto.common.ApiResponse;
import project.stylemate.dto.member.email.VerificationRequest;
import project.stylemate.enums.ReturnCode;
import project.stylemate.exception.SmLogicException;
import project.stylemate.exception.SmRequestException;
import project.stylemate.service.EmailService;
import project.stylemate.service.VerificationService;

@RestController
@RequiredArgsConstructor
public class EmailController {

    private final EmailService emailService;
    private final VerificationService verificationService;

    @PostMapping("/api/v1/users/send-verification-code")
    ApiResponse<?> sendEmail(@RequestBody String email) {
        String verificationCode = verificationService.generateVerificationCode();

        emailService.sendEmail(email, "이메일 인증", "인증 번호: " + verificationCode);

        verificationService.saveVerificationCode(email, verificationCode);

        return ApiResponse.of(ReturnCode.SUCCESS);
    }

    @PostMapping("/api/v1/users/verify")
    ApiResponse<?> verifyEmail(@RequestBody VerificationRequest request) {
        if (verificationService.verifyEmail(request.getEmail(), request.getVerificationCode())) {
            return ApiResponse.of(ReturnCode.SUCCESS);
        } else {
            throw new SmRequestException(ReturnCode.VERIFICATIONCODE_NOT_VALID);
        }
    }
}
  • sendEmail(): 랜덤의 인증번호를 생성하고 클라이언트에서 요청한 메일로 인증번호를 전송한다. 전송한 인증번호를 redis에 저장한다.
  • verifyEmail(): 클라이언트에서 넘어온 메일로 redis에 저장된 인증번호를 확인하여 클라이언트에서 넘어온 인증번호와 같은지 검증한다.

💡 docker에서 redis 설치

해당 프로젝트에서 redis를 처음 사용하기때문에 docker-compose파일을 통해 redis를 띄우는걸 잊으면 안된다.

먼저 아래 명령어를 통해 docker에서 redis를 설치하자

docker pull redis:alpine

설치가 완료되었다면 기존 docker-compose 파일에 redis 내용을 추가한다.

version: '3.8'

services:
  mysql:
    image: mysql:latest
    container_name: mysql_container
    environment:
      MYSQL_ROOT_USER: ${MYSQL_ROOT_USER}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: docker_mysql
    ports:
      - "3305:3306"
    command:
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:latest
    container_name: redis_container
    ports:
      - "6379:6379"

volumes:
  mysql_data:

cmd에서 프로젝트 경로에 docker-compose 파일을 실행시키면 redis가 잘띄워지는것을 확인할 수 있다.

profile
우엉이의 코딩 성장일기💻

0개의 댓글