들어가기
이전 포스팅에서 Spring Boot로 이메일 인증 요청 API를 구현했다.
이번에는 인증 코드에 만료 시간을 부여해서 Redis를 통해 인증 코드를 관리하는 로직을 추가하려고 한다.
먼저 Redis에 대해 가볍게 알아보자 !

💡 Redis란?
Redis (Remote Dictionary Server)
: Key-Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터베이스 관리 시스템이다.
데이터베이스, 캐시, 메시지 브로커로 사용되며, 인메모리(In-Memory) 데이터 구조를 가진 저장소이다.
✔️ 인메모리(In-Memory) 데이터 구조란?
- 컴퓨터 메인 메모리(RAM)에 데이터를 올려서 사용하는 구조이다.
- SSD, HDD 같은 저장 공간에서 데이터를 가져오는 것보다 RAM에 올려진 데이터를 가져오는 속도가 훨씬 빠르다.
- 인메모리 구조 특성상, 서버에 장애가 발생될 경우 데이터 유실이 발생될 수 있다.
✔️ Redis 특징
- Key-Value 형식이기 때문에 쿼리를 작성할 필요가 없다.
- 데이터를 디스크에 올리는 방식이 아니라 메모리에서 처리하기 때문에 속도가 빠르다. (인메모리)
- String, List, Set 등의 자료 구조를 지원한다.
- 싱글 스레드로, 한 번에 하나의 명령만 처리한다.
✔️ Redis 사용 시 주의할 점
- 인메모리 구조를 가지기 때문에 서버에 장애가 발생됐을 경우에 대한 대비를 해야 한다.
- 메모리 관리가 중요하다.
- 싱글 스레드 특성상, 한 번에 하나의 명령만 처리하기 때문에 처리하는데 시간이 오래 걸리는 요청/명령은 피해야 한다.
✔️ Redis 사용 이유
이메일 인증 코드 만료를 위해 Redis 방식을 선택했다. 이유가 무엇일까!
- 메모리에 데이터를 저장하기 때문에 빠른 속도로 데이터에 접근할 수 있다. 따라서, Redis를 캐시로 사용하면 매번 디스크 기반 데이터베이스에서 데이터를 읽어오는 비용을 아낄 수 있다.
- Redis는 TTL(Time-To-Live) 기능을 제공한다. 따라서, 데이터의 만료 시간을 설정할 수 있기 때문에 인증 코드 관리에 적합하다.
💡 Redis로 인증 코드 관리하기
1. docker-compose
redis:
container_name: 컨테이너 이름
image: redis:latest
ports:
- 6379:6379
volumes:
- ./redis/data:/data
- ./redis/conf/redis.conf:/usr/local/conf/redis.conf
labels:
- "name=redis"
- "mode=standalone"
restart: always
command: redis-server /usr/local/conf/redis.conf
2. build.gradle
Redis 의존성을 추가한다.
// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
3. application.yml
redis:
host: localhost
port: 6379
4. RedisConfig
@Configuration
public class RedisConfig {
@Value("${redis.host}")
private String host;
@Value("${redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
5. RedisRepository
public interface RedisRepository {
String getData(String key);
boolean existData(String key);
void setDataExpire(String key, String value, long timeout);
void deleteData(String key);
}
6. RedisRepositoryImpl
@Repository
public class RedisRepositoryImpl implements RedisRepository {
private final RedisTemplate<String, String> redisTemplate;
public RedisRepositoryImpl(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public String getData(String key) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}
@Override
public boolean existData(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
@Override
public void setDataExpire(String key, String value, long timeout) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
Duration duration = Duration.ofMillis(timeout);
valueOperations.set(key, value, duration);
}
@Override
public void deleteData(String key) {
redisTemplate.delete(key);
}
}
7. RedisService
@Service
public class RedisService {
private final RedisRepositoryImpl redisRepositoryImpl;
public RedisService(RedisRepositoryImpl redisRepositoryImpl) {
this.redisRepositoryImpl = redisRepositoryImpl;
}
@Transactional(readOnly = true)
public String getData(String key) {
return redisRepositoryImpl.getData(key);
}
@Transactional(readOnly = true)
public boolean existData(String key) {
return redisRepositoryImpl.existData(key);
}
@Transactional(readOnly = false)
public void setDataExpire(String key, String value, long timeout) {
redisRepositoryImpl.setDataExpire(key, value, timeout);
}
@Transactional(readOnly = false)
public void deleteData(String key) {
redisRepositoryImpl.deleteData(key);
}
}
8. EmailController
@GetMapping(value = "/auth", name = "이메일 인증 코드 확인")
public DataResponse<DataResponseCode> checkMailCode(@RequestParam("receiver") String receiver, @RequestParam("code") String code) {
DataResponse<DataResponseCode> response = emailService.verifyEmailCode(receiver, code);
return response;
}
9. EmailService
@Slf4j
@Service
public class EmailService {
...
private final RedisService redisService;
...
@Transactional(readOnly = false)
public DataResponse<DataResponseCode> sendMail(String receiver) {
try {
emailCode = createKey();
MimeMessage message = createMessage(receiver);
if (redisService.existData(receiver)) { // 기존에 발급 받았던 인증 코드 삭제
redisService.deleteData(receiver);
}
javaMailSender.send(message);
redisService.setDataExpire(receiver, emailCode, 3 * 60 * 1000L); // 만료 시간 3분
return new DataResponse<>(UserSignUpResponseCode.SUCCESS);
} catch (MessagingException | UnsupportedEncodingException | MailException e) {
e.printStackTrace();
return new DataResponse<>(UserSignUpResponseCode.MAIL_SEND_FAILED);
}
}
/**
* 메일 인증 코드 확인
*/
@Transactional(readOnly = true)
public DataResponse<DataResponseCode> verifyEmailCode(String email, String code) {
String userMailCode = redisService.getData(email);
if (userMailCode == null) {
return new DataResponse<>(UserSignUpResponseCode.EXPIRED_AUTH_MAIL_CODE);
}
if (userMailCode.equals(code)) { // 올바른 인증 코드인 경우
return new DataResponse<>(UserSignUpResponseCode.SUCCESS);
}
else { // 올바르지 않은 인증 코드인 경우
return new DataResponse<>(UserSignUpResponseCode.INVALID_AUTH_MAIL_CODE);
}
}
...
}
출처
긴 글 읽어주셔서 감사합니다 🍀
잘못 작성된 내용은 피드백 주시면 반영하겠습니다 😎
'Spring' 카테고리의 다른 글
[Spring] Spring Boot 이메일 인증 구현하기 + Redis (1) (0) | 2024.01.25 |
---|---|
[Spring] Spring Security와 Filter Chain (0) | 2024.01.10 |
[Spring] Spock 프레임워크 (0) | 2023.12.29 |
[Spring] Spring MVC 와 MVC2 (0) | 2023.12.17 |