본문 바로가기
DB/Redis

[Redis] Redis를 이용한 임시번호 발급(OTP, 임시비밀번호, 인증문자) - Spring Boot

by lucas_owner 2024. 3. 23.

 

 

목차

  1. Random String(임시 인증 번호 생성 OTP)
  2. Redis에 OTP(임시번호) 저장
  3. 유효 OTP 인증
  4. 추가적인 흐름

 

개요

임시 비밀번호 발급, 제한시간내에 발송된 SMS, LMS 와 같은 기능 구현을 위해 테스트 코드 작성.

 

요건

  1. 영어 대,소문자 + 숫자 조합 n자 Random String 
  2. 인증번호 발급 이후 n분 후 파기

 

환경

  1. Spring Boot 2.7.10
  2. Redis 7.0.10
  3. Docker 

기본적인 Spring Boot 환경 + Redis 연동 및 설정 완료 기준으로 작성합니다.

 

* 연동 설정 관련은 아래 포스팅 참고

https://lucas-owner.tistory.com/57

 

[Redis] Redis + Spring boot 연동 (2)

1. Redis + Spring Boot 연결 Spring Boot 에서 Spring-data-redis 라이브러리를 통해 활용해보는 방법을 알아보겠습니다. - Redis Docker 설치 방법은 이전 포스팅을 참고 하세요! https://lucas-owner.tistory.com/56 [Redis] R

lucas-owner.tistory.com

 

 

 

 

1. Random String (임시 인증 번호 OTP) 생성

- 요구 사항에 따른 임시 번호 발급 기능을 생각하면, Spring 진영에서는 Random 기능을 떠올리게 될것이다. 

나중에 요구사항이 변경될것을 고려하여 RandomStringUtil 클래스를 생성하여, 사용해도 될것이고 혹은
Random 한 값을 생성하는 라이브러리 사용을 고려 해보자.

 

- RandomStringUtil.class

// Custom Random String Generate Class
@Slf4j
public class RandomStringUtil {
    private static final String CHAR_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final String CHAR_LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
    private static final String CHAR_DIGITS = "0123456789";
    private static final String CHAR_SPECIAL = "!@#$%^&*";

    private static final String ALL_CHARS = CHAR_UPPERCASE + CHAR_LOWERCASE + CHAR_DIGITS + CHAR_SPECIAL;

    private static final int keyLength = 32; // 생성 길이

    public String generateRandomKey() {
        StringBuilder sb = new StringBuilder(keyLength);
        Random random = new SecureRandom();
        for (int i = 0; i < keyLength; i++) {
            int randomIndex = random.nextInt(ALL_CHARS.length());
            sb.append(ALL_CHARS.charAt(randomIndex));
        }

        log.info("Generated Random Key : {}", sb.toString());

        return sb.toString();
    }
}

 

 

- Apache Common Lang

// In Service Class
private String genOtpKey() {
        return RandomStringUtils.randomAlphanumeric(10); // Eng(Upper, Lower) + Number
    }

 

 

 

2. Redis 에 OTP(임시번호) 저장

- Random 한 문자열을 생성했다면, 다음 단계로 해당 문자열의 저장과, 만료시간을 Redis 에 설정하면 된다. 

 

- UserServie.class

/************
 * @info : Redis - User Service
 * @version : 1.0.0
 * @Description :
 ************/
@Service
@RequiredArgsConstructor
@Slf4j
public class FirstUserSerivce {

    private final UserRedisRepositroy repository;
    private final RedisTemplate redisTemplate;
    private final StringRedisTemplate stringRedisTemplate;

    private final String OTP_PREFIX = "OTP:";

	/**
     * @info : 임시 비밀번호 저장 및 Return(OTP)
     * @param userId
     * @return
     */
	public String requestOtp(String userId) {
        stringRedisTemplate.opsForValue().set(OTP_PREFIX + userId, genOtpKey(), 3 * 60, TimeUnit.SECONDS);

        log.info("Temporay Password set : {}", redisTemplate.opsForValue().get(userId));

        return (String)redisTemplate.opsForValue().get(userId);
    }

    // 임시 비밀번호 생성(OTP)
    private String genOtpKey() {
        return RandomStringUtils.randomAlphanumeric(10); // Eng(Upper, Lower) + Number
    }
}

 

- requestOtp() 를 통해, Redis 에 요청한 User의 ID + OTP KEY + 만료시간을 설정해주었다.

   => 해당 코드에서는 유효시간을 3min으로 지정, 3min 이후 파기.

 

- stringRedisTemplate 은 일반적으로 Key - Value 를 String 으로 저장 하는 경우 사용 하면된다. 

   따라서 key 값을 다시 가져오는 것 또한 redisTemplate가 아닌 stringRedisTemplate 을 사용하여도 된다.

 

- 만료시간 

BoundValueOperations<String, String> ops = stringRedisTemplate.boundValueOps(key);
    ops.set(value);
    ops.expire(expirationTime, TimeUnit.SECONDS);

- service 클래스 처럼 stringRedisTemplate.ops~ 에 각각 인자를 주어 만료시간과, 값을 설정하는 방법도 있지만

위의 코드처럼 각각 설정하는 방법도 존재한다. 

 

test Id값으로 OTP 발급.
Redis - OTP 저장

 

* Redis Key - 만료 까지 남은 시간 확인 방법 

redis-cli TTL {Key}

 

redis-cli 를 통해, 해당 key 의 남은 Expire 시간을 알 수 있다. 

초(sec)단위로 return 되며, Expire TIme 이 지정되지 않았다면 -2 또는 -1 을 return 한다.

 


RedisTemplate - StringRedisTemplate 차이

RedisTemplate

  • Key 값에 대해, 다양한 Value에 다양한 타입을 저장 할 수 있다.
    • String, List, Map, Set ,,, 
  • Key 또한 key Bound 를 통해, String 이 아닌 다양한 타입으로 지정 가능하다. 

 

StringRedisTemplate

  • Key - Value 를 String 타입으로 저장, 조회 한다. 
  • RedisTemplate 은 String 타입으로 저장하기 위해선 추가적인 설정이 필요하지만
    StringRedisTemplate 는 기본적 으로 <String, String> 을 지원한다.

 

3. 사용자의 요청으로 OTP Check 

- 각 프로젝트 별로 인증에 대한 비지니스 로직은 다르겠지만, 기본적으로 골조는 비슷하다. 

어떤 사용자인지 알기 위한 식별값(ID, 이름 등등), 추가적으로 발급된 OTP 

2가지만 있어도 구현하고자 하는 기능을 만들 수 있다. 

 

위에서 보았던 똑같은 UserService.class 에 Method 를 추가해준다. (인증)

    /**
     * @info : 임시 비밀번호 확인 (OTP)
     * @param id
     * @param otp
     * @return
     */
    public String checkOtp(String id, String otp) {

        String target = OTP_PREFIX + id;

        if(stringRedisTemplate.hasKey(OTP_PREFIX + id)){
            String value = stringRedisTemplate.opsForValue().get(target);

            if(value.equals(otp)) {
                log.info("OTP is Correct");
                return "SUCCESS";
            }else {
                return "FAIL";
            }
        }else {
            return "NO DATA";
        }
    }

 

- 해당 코드에서는 쉬운 이해를 위해, return 값을 String 으로 설정하였고, 추가적인 다른 기능을 덧붙이거나, return 값을 각 도메인별로 

수정, 사용하면 된다. 

 

- hasKey() 메서드를 사용하여, 특정 사용자에 대한 Key 가 존재하는지(혹은 만료되지 않았는지) 확인 이후, key 값이 존재 한다면
요청한 OTP 의 값이 일치하는지 확인 하는 간단한 로직이다. 

 

 

OTP 확인

 

4. 추가적인 흐름

- 지금까지의 코드블록 3개만 사용해도, 임시 번호에 대한 발급 및 인증에 대해 쉽게 구현할 수 있고 

추가적으로 SMS를 보낸다던가, Email 을 보낸다던가 하는 로직에 대해서는 구현 하고자 하는 요구 사항에 맞춰 진행 하면된다 

 

- UserService.requestOtp()

    /**
     * @info : 임시 비밀번호 요청 (OTP)
     * @param userId
     * @return
     */
    public boolean requestOtp(String userId) {
		boolean sendFlag = false;
        
		stringRedisTemplate.opsForValue().set(OTP_PREFIX + userId, genOtpKey(), 3 * 60, TimeUnit.SECONDS);

		// Email 
        // emailService.sendEmail(userId, stringRedisTemplate.opsForvalue().get(userId));
        
        // SMS
        sendFlag = smsService.sendSms(userId, stringRedisTemplate.opsForvalue().get(userId));
        
        // TODO: 각 요청 sendEmail, sendSms에 대한 return value 로직 추가.

        return sendFlag;
    }

 

 


현재도 운영되고 있는 많은 사이트들에서 사용되고 있는, 임시번호 발급 및 인증 기본에 대한 간단한 코드로 확인을 해보았고

해당 기능 개발에 관련하여, 이것저것 새로 알게 된것이 생겼다. (보안을 위해, 비밀번호 변경이 아닌 임시비밀번호 발급 ->  로그인 -> 비밀번호 변경) 

 

* 추가로 고려할 점이나, 좋은 아이디어가 있다면 공유해주시면 감사하겠습니다

반응형

댓글