공부/Spring

[공부/Spring] 구글 SMTP를 이용한 이메일 인증

yulee_to 2023. 7. 23. 18:40

 

스프링

운영체제 : MacOS M1
IDE : IntelliJ
Spring Boot : 2.7.8
Java : 11
JDBC : MyBatis

계기

추후에 아이디나 비밀번호를 찾을 때 이메일을 이용하기 위해 회원가입시 이메일 인증 과정을 추가하고자 한다.

이메일 인증은 회원가입 뿐만 아니라 비밀번호 재설정시 본인임을 확인하기 위한 용도로도 사용할 예정이다.

 

SMTP란?

SMTP(Simple Mail Transfer Protocol)은 네트워크를 통해 전자우편(이메일)을 전송하는 기술 표준을 의미한다. 

이메일 인증을 위해서 SMTP를 통해 원하는 계정으로 인증코드를 담은 이메일을 전송할 것이다. 

SMTP를 사용하기 위해 build.gradle에 아래 의존성을 추가해주자.

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

 

Redis란?

https://en.wikipedia.org/wiki/Redis

Redis는 Key-Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈소스 기반의 비관계형 데이터베이스 관리 시스템이다.

Redis는 Key-Value 구조라 쿼리를 사용할 필요가 없고, 데이터를 메모리에서 처리하기 때문에 DB처럼 디스크에 접근하지 않아도 되어 속도가 빠르다는 장점이 있다. 싱글 스레드로 한번에 하나의 명령만 처리할 수 있어 처리 시간이 긴 명령어가 들어오면 그 뒤에 명령어들이 대기하는 시간이 길어진다는 단점이 있다. 

 

Redis의 영속성

Redis는 기본적으로는 메모리에만 데이터를 저장하기 때문에 서버가 내려가면 Redis에 저장된 데이터도 유실된다. 

서버가 내려가도 데이터가 유실되지 않도록 하려면 어떻게 해야 할까?

다행히 Redis는 영속성을 보장하기 위해 데이터를 디스크에 저장할 수 있다. 그래서 서버가 내려가더라도 디스크에 저장된 데이터를 읽어서 메모리에 로딩해올 수 있다. Redis에서 제공하는 영속성 옵션에는 두가지가 있다.

  • RDB : 명시된 간격마다 dateset의 해당 시점의 스냅샵을 수행
  • AOF : 서버로 수신된 모든 쓰기 작업을 기록하고, 서버 재시작 시 수행돼서 원래의 데이터셋을 재구축

물론 서버가 수행중인 동안에만 데이터를 존재하게 하려면 영속성을 완전히 비활성화할 수도 있다. 

 

Redis를 사용하는 곳

주로 Cache 서버를 구현할 때 많이 사용한다. 

이 프로젝트에서는 이메일 인증 코드를 제한 시간 동안 Redis에 저장해뒀다가 제한 시간이 지나면 인증코드를 맞게 입력해도 인증이 안되게 할 것이다. 

 

Redis를 사용하기 위해 build.gradle에 다음 코드를 추가해야 한다. (Redis가 로컬에 설치되어 있어야 함) 

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

 

구글 SMTP 사용법

1. google 로그인을 하고 Chrome의 메인 페이지에서 프로필 사진을 누르면 위 사진처럼 여러 항목이 보인다.

여기서 Google 계정 관리를 선택한다. 

 

2. 보안 탭에 들어가서 2단계 인증을 눌러 인증을 해준다. 

 

3. 2단계 인증을 사용하도록 설정했다는 페이지가 뜨면 해당 페이지 맨 아래에 앱 비밀번호라는 항목을 눌러준다. 

 

4. 앱은 메일로 선택해주고, 기기는 기억하기 편한 이름으로 지정해주면 위와 같은 팝업이 뜬다.

이 때 저 노란 박스의 코드를 반드시 따로 저장해둬야 한다!! 

저 코드가 서버에서 접속할 때 필요한 비밀번호다.

 

설정 파일

application.yml

spring:
	mail:
        host: smtp.gmail.com
        port: 587
        username: {사용할 이메일 주소}
        password: {위 단계에서 받아온 앱 비밀번호}
        properties:
          mail:
            smtp:
              auth: true
              timeout: 5000
              starttls:
                enable: true

application.properties

spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username={사용할 이메일 주소}
spring.mail.password={위 단계에서 받아온 앱 비밀번호}
spring.mail.properties.mail.smtp.auth = true
spring.mail.properties.mail.stmp.timeout = 5000
spring.mail.properties.mail.smtp.starttls.enable = true

- host : smtp 서버 ip로 구글의 경우 stmp.gmail.com 사용

- port : smtp port 번호로 구글의 경우 587 사용

- properties.mail.stmp.timeout : Socket Read Timeout 시간(ms), 기본값은 무한대

- properties.mail.smtp.auth : true인 경우 사용자 인증을 시도함, 기본값은 false

- properties.mail.smtp.starttls.enable : 메일 발송시 STARTTLS를 이용한 명시적 보안을 사용하려면 true를, SSL/TLS를 이용한 암시적 보안을 사용하려면 false로 지정, 기본값은 false

 

MailService

@Service
@RequiredArgsConstructor
public class MailService {
	private final JavaMailSender mailSender; // 메일 발송을 기능을 위한 클래스 
	private final RedisService redisService; // Redis에 데이터를 저장, 삭제, 조회해오는 로직이 담긴 클래스
	private final MailConstant mailConstant; // mail과 관련된 설정 정보를 담아놓은 클래스

	public void sendAuthEmail(String email) throws MessagingException {
		String authCode = createAuthCode(mailConstant.AUTH_CODE_LENGTH); // 인증 코드 생성

		MimeMessage mail = mailSender.createMimeMessage();
		String mailContent = "<h1>[이메일 인증]</h1>"
			+ "<br>"
			+ "<h3>이메일 인증 번호 : " + authCode + "</h3>";

		mail.setSubject("회원가입 이메일 인증 ", "utf-8"); // 메일 제목 지정
		mail.setText(mailContent, "utf-8", "html"); // 메일 내용 지정
		mail.addRecipient(Message.RecipientType.TO, new InternetAddress(email)); // 파라미터로 받은 email을 전송할 email 주소로 설정
		try {
			mailSender.send(mail); // 이메일 전송
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		redisService.setDataExpire(email, authCode, mailConstant.EXPIRE_TIME); // 만들어낸 인증 코드를 제한시간을 지정해 Redis에 저장
	}

    // RedisService의 checkData 메소드를 통해 파라미터로 받은 email에 해당하는 인증코드가 Redis에 저장되어 있는지 확인하는 메소드 
	public void checkEmailAuthCode(String email, String authCode) {
		if (!redisService.checkData(email, authCode)) {
			throw new NoMatchedKeyException(ErrorCode.NO_MATCHING_AUTH_CODE);
		}
	}
    
    // 파라미터로 받은 길이만큼의 랜덤한 숫자를 생성해서 String 형태로 반환하는 메소드 
	private String createAuthCode(int size) {
		Random random = new Random();
		StringBuffer buffer = new StringBuffer();
		int num = 0;

		while (buffer.length() < size) {
			num = random.nextInt(10);
			buffer.append(num);
		}

		return buffer.toString();
	}

}

 

RedisService

@Service
@RequiredArgsConstructor
public class RedisService {
	private final StringRedisTemplate stringRedisTemplate;

	// key로 Redis에 저장된 데이터 가져오는 메소드
	public String getData(String key) {
		ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
		return valueOperations.get(key);
	}
    
	// key-value를 제한시간을 두어 저장하는 메소드 
	public void setDataExpire(String key, String value, long duration) {
		ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
		Duration expireDuration = Duration.ofSeconds(duration);
		valueOperations.set(key, value, expireDuration);
	}

	// key에 해당하는 데이터를 가져오고 그 데이터가 value와 일치하는지 확인, 일치하면 redis에서 해당 key-value 쌍을 삭제하고 true 반환
	public boolean checkData(String key, String value) {
		String data = getData(key);

		if (data != null && data.equals(value)) {
			deleteData(key);
			return true;
		}
		return false;
	}

	// redis에서 key 값에 해당하는 데이터를 삭제 
	public void deleteData(String key) {
		stringRedisTemplate.delete(key);
	}
}

 

Service 단은 위와 같이 구현하고, Controller에서 해당 로직들을 적절하게 사용하면 다음과 같이 인증 코드가 전송된다. 

정리

이메일 인증 코드를 보낼 땐 Redis를 이용하면 DB에 저장할 필요가 없고, 빠르게 탐색이 가능하고 제한시간을 지정해줄 수 있어 사용하기 용이하다. 이메일 인증 이외에도 사용자에게 알리고자 하는 안내 사항 등도 SMTP를 이용하여 처리하면 된다. 

728x90