임시 인증 번호 개발 목차
- 테스트 코드 작성
- 로직 구현
구현 내용
비밀번호 찾기 및 임시 비밀번호 발급을 위한 임시 인증 번호 개발
요구 분석
- 특수문자 포함 총 8자 구현
- 인증번호 발급 후 3분 후 파기
여기서 가장 큰 문제점은 3분 후 파기라는 것이었다.
주 DB로 사용하고 있는 RDBMS를 사용하게 되면, 편하게 CRUD를 할 수 있겠지만 3분 후 파기를 구현하기 위해서는 Timer를 만들어 시간이 되면 DELETE or Update 처리를 해줘야 했다. 이것의 해결점으로 Redis를 사용하여 키, value 값으로 메모리에 저장하는 방법을 택했다.
실제 구현
- 영문자+특수문자+숫자를 통해 8자리의 임시번호 발급
- Redis에 해당 값을 <Key, Value> 값으로 저장
- 해당 값이 맞는지 검증
임시번호 발급
먼저, 테스트 코드를 작성해 보았다.
-임시번호 Test Code
@Test
@DisplayName("임시 비밀번호 생성")
void create_temporary_password() {
------------------------임시번호 발급 ---------------------
String temporaryPassword = tempNumberService.createTemporaryNumber();
String temporaryPassword2 = tempNumberService.createTemporaryNumber();
//then
assertThat(temporaryPassword).isNotEmpty();
assertThat(temporaryPassword).isNotEqualTo("");
assertThat(temporaryPassword).isInstanceOf(String.class);
assertThat(temporaryPassword.length()).isLessThan(9);
assertThat(temporaryPassword2).isNotEqualTo(temporaryPassword);
}
간단하게 코드를 살펴보면,
9자리 수보다 작은 String 형이고, temporayPassword != temporayPassword2 가 달라야 한다.
테스트 코드를 작성하였으니, 이 테스트 코드가 성공하도록 로직을 구현해보자.
-임시번호 Service Code
public String createTemporaryNumber() {
SecureRandom random = new SecureRandom();
final String passwordList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < passwordLength; i++) {
int randomIndex = random.nextInt(passwordList.length());
sb.append(passwordList.charAt(randomIndex));
}
return sb.toString();
}
첫 번째 줄을 보면, SecureRandom을 사용하였다.
이전까지 Random 정수를 발생시킬 때, Math.Random을 사용하였지만, 취약점이 많다고 하였다.
그 이유로는 Random 함수 같은 경우, 시드 값을 설정할 수 없어 알고리즘이 밝혀지면 취약해진다고 한다.
정확하지 않아, 해당 내용을 참고 문헌에 추가하겠다.
그다음, 이 코드들은 먼저 정의된 passwordList에서 SecureRandom 값을 이용하여 값을 뽑아 문자열을 생성한다.
여기서 같이 프로젝트하는 팀원이 새로운 방법을 제시했다.
이렇게 Az, 0~9 , 특수키 호를 하나씩 다 적는 방법보다는 정수형을 문자로 바꾸는 방법으로 하시는 게 어떨까요? 65가 대문자 A니깐 아스키코드값에 대해 검색해보시고 더 나은 방법대로 수정해주세요!
해당 의견이 내가 생각하기에도 좋은 방법인 거 같아, 테스트를 진행해 보았다.
@Test
void createTemporaryNumber() {
long start = System.currentTimeMillis();
SecureRandom random = new SecureRandom();
final String passwordList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$";
System.out.println("passwordList.length() = " + passwordList.length());
StringBuilder sb = new StringBuilder();
for (int i = 0; i < passwordLength; i++) {
int randomIndex = random.nextInt(passwordList.length());
sb.append(passwordList.charAt(randomIndex));
}
long end = System.currentTimeMillis();
System.out.println("수행시간 : " + (end - start) + " ms");
}
@Test
void createTemporaryNumberAscii() {
long start = System.currentTimeMillis();
SecureRandom random = new SecureRandom();
int max = 126;
int min = 33;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < passwordLength; i++) {
int randomIndex = random.nextInt((max - min) + 1) + min;
sb.append(Character.toString(randomIndex));
}
long end = System.currentTimeMillis();
System.out.println("수행시간 : " + (end - start) + " ms");
}
Ascii-> String 수행시간 : 229 ms
Static -> 수행시간 : 49 ms
Process finished with exit code 0
10000자리 수의 임시번호를 만들었을 때, 6배 이상 차이가 났다.
여기서 차이가 나는 이유는 Ascii -> String으로 변환하는 과정에서 성능 차이가 나는 것 같았다.
임시번호 Redis에 저장
-Redis Create Test Code
@Test
@DisplayName("임시번호를 발급하여 Redis 3분동안 저장")
void savedTempNumber() {
String reqUser = "test";
SavedTempNumberResponse response = tempNumberService.savedTempNumber(reqUser);
assertThat(redisTemplate.opsForValue().get(reqUser)).isNotNull();
assertThat(redisTemplate.opsForValue().get(reqUser)).isEqualTo(response.getTempNumber());
}
간단하게 보면, 임시번호를 <사용자명, 임시번호> 형식으로 Redis에 저장한다.
그리고 그것을 메모리에 빼와 검증하게 된다. 이것을 성공하게 하는 비즈니스 코드를 작성해보자.
-Redis Create Service Code
public SavedTempNumberResponse savedTempNumber(String reqUser) {
String temporaryNumber = createTemporaryNumber();
redisTemplate.opsForValue().set(reqUser, temporaryNumber);
redisTemplate.expire(reqUser, 3, TimeUnit.MINUTES);
return new SavedTempNumberResponse(reqUser, temporaryNumber);
}
이 코드는, 이전에 구현한 임시번호를 갖고 와 <사용자명, 임시번호> 형식으로 메모리를 저장한다.
여기서 중요한 것은, redisTemplate.expire()이라는 함수이다.
이것은 요구분석 2단계, 즉 3분 후 파기라는 조건을 만족할 수 있다.
임시번호 검증
matchesTempNumber Test Code
@Test
@DisplayName("임시번호와 사용자요청 임시번호 일치")
void matchesTempNumber() {
String reqUser = "test";
SavedTempNumberResponse response = tempNumberService.savedTempNumber(reqUser);
Boolean aBoolean = tempNumberService.matchesTempNumber(reqUser, response.getTempNumber());
assertThat(aBoolean).isEqualTo(Boolean.TRUE);
}
위에 테스트 코드 중 가장 쉽다.
Key 값을 통해 Value를 갖고와 두 값이 같은지 비교한다.
matchesTempNumber Service Code
public Boolean matchesTempNumber(String reqUser, String reqTempNumber) {
if (redisTemplate.hasKey(reqUser)) {
String keyTempNumber = redisTemplate.opsForValue().get(reqUser);
return keyTempNumber.equals(reqTempNumber);
}
return false;
}
redisTemplate.hasKey를 통해 해당 값이 Redis에 있는지 확인 후,
갖고 와 비교한 후 Boolean을 반환한다.
후기
이와 같이 Redis를 통해 3분 후 파기되는 임시번호 구현이 끝났다 😀
써야지.. 써야지.. 하고 1주일이 지난 것 같은데, 밀린 숙제를 끝낸 기분이다.
이제, 회사 선임분들이 작성하신 귀중한 자료를 보러 가야겠다 🖐
추가 문헌
https://jungpaeng.tistory.com/59
Math.random()은 정말 랜덤일까?
[JavaScript] Math.random()은 정말 랜덤일까? TL; DR; 자바스크립트는 어떻게 Math.random 함수에서 난수를 생성하는가? JS는 아무것도 하지 않는다. 브라우저에 따라 달라진다. 현재 대부분의 브라우저는 xor
jungpaeng.tistory.com
'토이프로젝트' 카테고리의 다른 글
[9-in] Spring Boot + Kotlin 글로벌 이셉션 구현 (커스텀 이셉션) (0) | 2023.03.12 |
---|---|
[졸업 작품] 연령 별 제품 추천 SQL (0) | 2022.09.12 |