[SpringBoot] CoolSMS를 통해 프로젝트에 휴대폰 인증 기능 구현[2]

2024. 11. 12. 22:41·SpringBoot

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
'SpringBoot' 카테고리의 다른 글
  • [SpringBoot] MSA 구조 구현에 필요한 기술들
  • [SpringBoot] AWS EC2, Nginx를 사용한 SpringBoot 서버 리버스 프록시 설정
  • [SpringBoot] CoolSMS를 통해 프로젝트에 휴대폰 인증 기능 구현[1]
  • [SpringBoot] AWS EC2, Jenkins를 사용해 깃허브 웹훅 연결하기
l0o0lv
l0o0lv
  • l0o0lv
    개발하는 와플대조교
    l0o0lv
  • 전체
    오늘
    어제
    • 분류 전체보기 (60)
      • SpringBoot (10)
      • 잡다한 개발지식 (1)
      • 코딩테스트 (47)
        • SWEA (43)
        • 프로그래머스 (0)
        • 백준 (4)
      • 알고리즘 (1)
      • 회고 (0)
      • 운동 (0)
  • 링크

    • Github
  • 인기 글

  • 최근 글

  • 태그

    백준
    합 배열
    springboot
    실시간 쪽지
    jenkins
    websocket
    구간 합
    어려웠던 문제
    ec2
    cloudtype
    coolsms
    무중단 배포
    알고리즘
    prepersist
    SWEA
    MSA
    EUREKA
    리버스 프록시
    webhook
    gitlab
  • hELLO· Designed By정상우.v4.10.0
l0o0lv
[SpringBoot] CoolSMS를 통해 프로젝트에 휴대폰 인증 기능 구현[2]
상단으로

티스토리툴바