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;
}
}
RedisTemplate
에 opsForValue().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에 저장된 인증번호를 확인하여 클라이언트에서 넘어온 인증번호와 같은지 검증한다.해당 프로젝트에서 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가 잘띄워지는것을 확인할 수 있다.