목차
개요
임시 비밀번호 발급, 제한시간내에 발송된 SMS, LMS 와 같은 기능 구현을 위해 테스트 코드 작성.
요건
- 영어 대,소문자 + 숫자 조합 n자 Random String
- 인증번호 발급 이후 n분 후 파기
환경
- Spring Boot 2.7.10
- Redis 7.0.10
- Docker
기본적인 Spring Boot 환경 + Redis 연동 및 설정 완료 기준으로 작성합니다.
* 연동 설정 관련은 아래 포스팅 참고
https://lucas-owner.tistory.com/57
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~ 에 각각 인자를 주어 만료시간과, 값을 설정하는 방법도 있지만
위의 코드처럼 각각 설정하는 방법도 존재한다.
* 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 의 값이 일치하는지 확인 하는 간단한 로직이다.
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;
}
현재도 운영되고 있는 많은 사이트들에서 사용되고 있는, 임시번호 발급 및 인증 기본에 대한 간단한 코드로 확인을 해보았고
해당 기능 개발에 관련하여, 이것저것 새로 알게 된것이 생겼다. (보안을 위해, 비밀번호 변경이 아닌 임시비밀번호 발급 -> 로그인 -> 비밀번호 변경)
* 추가로 고려할 점이나, 좋은 아이디어가 있다면 공유해주시면 감사하겠습니다
'DB > Redis' 카테고리의 다른 글
[Redis] Spring Boot - Redis Pub, Sub 구현&응용 (3) | 2024.03.29 |
---|---|
[Redis] Redis - pub/sub 이란? (1) | 2023.07.16 |
[Redis] Redis + Spring boot 연동 (2) (2) | 2023.03.30 |
[Redis] Redis란? - Docker로 간단 Redis(Local) 설치 (1) (1) | 2023.03.29 |
댓글