Redis
휴대폰번호로 도착한 인증번호를 잠시 저장할 공간이 필요하지만 MySQL과 MariaDB와 같은 RDBMS를 사용하는 것은 비효율적이라 생각했다.
그래서 방법을 찾아보던 중, Redis에 대해 알게 되었다.
`Redis`는 고성능의 키-값 저장소로 사용되는 오픈 소스 인메모리 데이터 구조 서버다.
Redis는 Remote Dictionary Server의 약자로, 주로 빠른 데이터 접근 속도와 높은 처리량이 필요한 응용 프로그램에서 사용된다.
Redis의 특징
1. `인메모리 데이터 저장` : Redis는 모든 데이터를 메모리에 저장하고, 필요에 따라 디스크에 백업한다. 이로 인해 매우 빠른 데이터 접근이 가능하다.
2. `다양한 데이터 구조` : Redis는 문자열, 리스트, 셋, 해시, 정렬된 셋, 비트맵, 하이퍼로그로그 등의 다양한 데이터 구조를 지원한다. 이는 단순한 키-값 저장소 이상의 기능을 제공하며, 복잡한 데이터 처리 작업을 효율적으로 수행할 수 있다.
3. `고가용성 클러스터` : Redis는 Redis Cluster를 통해 데이터 분산과 샤딩을 지원하여 대규모 데이터를 효과적으로 처리할 수 있다.
4. `트랜잭션` : Redis는 MULTI, EXEC, DISCARD, WATCH 명령어를 통해 간단한 트랜잭션을 지원한다.
5. `TTL (Time to Live)` : Redis는 각 키에 대해 TTL을 설정할 수 있습다. TTL이 만료되면 Redis는 자동으로 데이터를 삭제한다.
Redis는 이러한 특징들로 인해 캐싱, 세션 저장, 실시간 분석, 메시지 큐, 랭킹 시스템 등 다양한 용도로 널리 사용된다.
대표적인 사용 사례로는 웹 애플리케이션의 세션 관리, 실시간 채팅 애플리케이션, 게임 리더보드, 분석 데이터의 일시적 저장 등이 있다.
나는 1번과 5번의 특징을 보고 인증번호를 잠시동안 저장할 공간으로 Redis를 사용하게 되었다.
구현
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Controller
@PostMapping("/verify")
public ResponseEntity<?> verifyCode(@RequestBody @Valid SmsVerifyDto smsVerifyDto){
boolean verify = smsService.verifyCode(smsVerifyDto);
if (verify) {
return ResponseEntity.ok("인증이 되었습니다.");
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("인증에 실패했습니다.");
}
}
application.properties
spring.data.redis.host=localhost
spring.data.redis.port=6379
Dto
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class SmsVerifyDto {
@NotNull(message = "휴대폰 번호를 입력해주세요.")
private String phoneNum;
@NotNull(message = "인증번호를 입력해주세요.")
private String certificationCode;
}
Config
@Configuration
@RequiredArgsConstructor
@EnableRedisRepositories // Redis 레포지토리 기능 활성화
public class RedisConfig {
private final RedisProperties redisProperties; // Redis 속성 정보 주입
@Bean // 스프링 컨텍스트에 RedisConnectionFactory 빈 등록
public RedisConnectionFactory redisConnectionFactory(){
// LettuceConnectionFactory를 사용하여 Redis 연결 팩토리 생성, 호스트와 포트 정보를 사용
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); // RedisTemplate 인스턴스 생성
redisTemplate.setConnectionFactory(redisConnectionFactory()); // Redis 연결 팩토리 설정
redisTemplate.setKeySerializer(new StringRedisSerializer()); // 키를 문자열로 직렬화하도록 설정
redisTemplate.setValueSerializer(new StringRedisSerializer()); // 값을 문자열로 직렬화하도록 설정
return redisTemplate; // 설정이 완료된 RedisTemplate 인스턴스를 반환
}
}
Redis Repository(Sms Repository)
@RequiredArgsConstructor
@Repository
public class SmsRepository {
private final String PREFIX = "sms:"; // Redis 키에 사용할 접두사
private final StringRedisTemplate stringRedisTemplate; // Redis 작업을 위한 StringRedisTemplate 객체
// SMS 인증 정보를 생성하는 메서드
public void createSmsCertification(String phone, String code){
int LIMIT_TIME = 3 * 60; // 인증 코드의 유효 시간(초), 3분 설정
stringRedisTemplate.opsForValue()
.set(PREFIX + phone, code, Duration.ofSeconds(LIMIT_TIME)); // Redis에 키와 값을 설정, 유효 시간도 함께 설정
}
// SMS 인증 정보를 가져오는 메서드
public String getSmsCertification(String phone){
return stringRedisTemplate.opsForValue().get(PREFIX + phone); // Redis에서 키에 해당하는 값을 가져옴
}
// SMS 인증 정보를 삭제하는 메서드
public void deleteSmsCertification(String phone){
stringRedisTemplate.delete(PREFIX + phone); // Redis에서 해당 키를 삭제
}
// 해당 키가 존재하는지 확인하는 메서드
public boolean hasKey(String phone){
return Boolean.TRUE.equals(stringRedisTemplate.hasKey(PREFIX + phone)); // Redis에서 키의 존재 여부를 확인
}
}
Service
@Service
public class SmsServiceImpl implements SmsService {
private final SmsCertificationUtil smsCertificationUtil; // SMS 인증 유틸리티 객체
private final SmsRepository smsRepository; // SMS 레포지토리 객체 (Redis)
private final AuthFeignClient authFeignClient; // 인증을 위한 Feign 클라이언트 객체
// 의존성 주입
public SmsServiceImpl(@Autowired SmsCertificationUtil smsCertificationUtil,
AuthFeignClient authFeignClient,
SmsRepository smsRepository) {
this.smsCertificationUtil = smsCertificationUtil;
this.smsRepository = smsRepository;
this.authFeignClient = authFeignClient;
}
@Override // SmsService 인터페이스의 메서드를 구현
public void SendSms(SmsRequestDto smsRequestDto) {
String phoneNum = smsRequestDto.getPhoneNum(); // SmsRequestDTO에서 전화번호를 가져옴
AuthResponseDto authResponseDto = authFeignClient.checkPhoneNum(phoneNum); // 전화번호의 가입 여부 확인
if (!authResponseDto.getNickname().equals("가입되지 않은 번호")) { // 이미 가입된 번호인지 확인
throw new IllegalArgumentException("이미 가입된 번호입니다."); // 가입된 번호일 경우 예외를 던짐
}
String certificationCode = Integer.toString((int)(Math.random() * (999999 - 100000 + 1)) + 100000); // 6자리 인증 코드를 랜덤으로 생성
smsCertificationUtil.sendSMS(phoneNum, certificationCode); // SMS 인증 유틸리티를 사용하여 SMS 발송
smsRepository.createSmsCertification(phoneNum, certificationCode); // 인증 코드를 Redis에 저장
}
@Override // SmsService 인터페이스의 메서드를 구현
public boolean verifyCode(SmsVerifyDto smsVerifyDto) {
if (isVerify(smsVerifyDto.getPhoneNum(), smsVerifyDto.getCertificationCode())) { // 인증 코드 검증
smsRepository.deleteSmsCertification(smsVerifyDto.getPhoneNum()); // 검증이 성공하면 Redis에서 인증 코드 삭제
return true; // 인증 성공 반환
} else {
return false; // 인증 실패 반환
}
}
// 전화번호와 인증 코드를 검증하는 메서드
public boolean isVerify(String phoneNum, String certificationCode) {
return smsRepository.hasKey(phoneNum) && // 전화번호에 대한 키가 존재하고
smsRepository.getSmsCertification(phoneNum).equals(certificationCode); // 저장된 인증 코드와 입력된 인증 코드가 일치하는지 확인
}
}
이전 게시글의 Service 코드에서 많이 변경되었다.
나는 MSA 구조로 프로젝트를 개발하고 있어서 가입된 번호가 없다면 authserver에서 responseDto의 nickname에 가입되지 않은 번호를 넣고 반환해 문자를 보내게 했다.
모놀리식 구조의 경우는 `userEntity.findByphoneNum` 메소드를 사용하면 될 것이다.
- Redis 설치
로컬에 레디스를 설치해주어야 한다.
Windows에서 레디스 설치는 다음 블로그를 참고하였다.
https://pamyferret.tistory.com/9
[ Redis ] Window에 redis 설치해서 사용하기
스프링 부트의 캐싱 기능을 사용하다보니 분산 캐싱 기능도 사용해보고 싶어졌다. 분산 캐싱에 사용할 캐시 데이터 저장 공간은 여러 가지를 사용하는데, 그 중 Redis라는 것을 사용해보려고 한
pamyferret.tistory.com
문자를 전송하면 Redis에 Key : {PhoneNum}, Value : {certificationCode} 형식으로 3분동안 저장된다.
이 인증코드를 입력하게 되면 isVerfy 메서드에서 검증 후 성공 실패를 반환하게 된다.
'SpringBoot' 카테고리의 다른 글
[SpringBoot] MSA 구조 구현에 필요한 기술들 (2) | 2024.11.12 |
---|---|
[SpringBoot] AWS EC2, Nginx를 사용한 SpringBoot 서버 리버스 프록시 설정 (1) | 2024.11.12 |
[SpringBoot] CoolSMS를 통해 프로젝트에 휴대폰 인증 기능 구현[1] (6) | 2024.11.12 |
[SpringBoot] AWS EC2, Jenkins를 사용해 깃허브 웹훅 연결하기 (0) | 2024.11.12 |
[SpringBoot] AWS EC2 Amazon Linux 인스턴스에 젠킨스 설치하기 (0) | 2024.11.12 |