[Spring boot] 문자 SMS 인증 구현하기(2)
2021.12.22 - [Back-end/Spring & Spring Boot] - [Spring boot] 문자 SMS 인증 구현하기(1)
이전 포스팅에 SMS 문자로 인증 번호를 발송하는 부분을 구현해보았는데 이번 포스팅엔 발송했던 인증 번호를 저장해 두었다가 인증 번호 확인을 하는 부분을 구현해보려고 한다.
휴대폰이나 이메일 인증을 통해 회원가입을 진행할때 고민해야할 가장 큰 부분은 인증번호 관리라고 생각한다.
기본적으로 생각해볼 수 있는 방법은 회원 테이블에서 관리하는 것이었지만 이 방식의 경우 한 번 인증 후 다시 인증을 할 일도 없고 인증번호의 경우 짧은 시간 내에 인증이 이뤄져야 했기에 RDB에서 관리하는 것은 비효율적이라고 생각하여 배제했다.
그에 비해 Redis는 기본적으로 Map형태의 key,value값을 지원하며 인증번호의 유효시간 또한 TTL로 간편히 설정할 수 있다. 무엇보다도 Disk에 접근하지 않고 데이터를 바로 처리할 수 있기 때문에 성능적인 부분에서도 이점이 있다.
Redis에 유효 기간을 3분으로 설정해서 set형태로 저장해두는 방식으로 구현했다. 한 사용자(한 번호) 당 인증 번호 정보를 1개를 갖고 있어야 하고, 인증 번호가 유효한 기간이 짧아야 하기에 이런 식으로 구현했다.
구현 과정을 살펴보자.. 먼저 로컬에서 테스트하려면 내 컴퓨터에 Redis를 설치해주어야 한다.
Redis 설치
https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
Release 3.2.100 · microsoftarchive/redis
This is the first release of Redis on Windows 3.2. This release is based on antirez/redis/3.2.1 plus some Windows specific fixes. It has passed all the standard tests but it hasn't been tested in a...
github.com
위 사이트에 들어가서 Window의 경우 해당 파일을 다운 받아주면 된다.
다운 받은 파일을 클릭하면 다음과 같은 화면이 나오는데 대부분 그냥 Next를 누르면 되고 중간에 뭐 환경설정 path에 추가한다는 체크박스? 같은거 있는데 그거는 체크해줬다.
cmd창에서 netstat -an | findstr 6379 를 입력하고 다음과 같이 뜬다면 잘 작동되고 있는 것이다. 설치가 잘 되었다면 이제 프로젝트를 구성해보자.
Spring boot 프로젝트에서의 구현
build.gradle
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '2.4.10'
gradle에 다음을 추가한다.
application.properties
spring.redis.host=localhost
spring.redis.port=6379
properties에 root source를 지정해줘야 한다.
RedisConfig.java
RedisConfig 파일을 하나 만들어야 한다. 설정 방법은 무수히 많은데 나는 다음과 같이 해줬고 잘 작동되었다.
@Configuration
@RequiredArgsConstructor
@EnableRedisRepositories
public class RedisConfig {
private final RedisProperties redisProperties;
// lettuce
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
SmsCertification.java
인증번호와 관련된 정보들을 저장, 삭제, 조회하기 위해 Repository를 하나 만들었다. 각 함수 설명은 주석 참고
아 Redis에 저장하는 부분에서 .set을 쓰는데 이렇게 쓰면 중복 방지가 자동으로 되어 젤 나중에 발급된 인증번호 하나만 저장된다. 키 중복 허용 불가! -> 즉 하나의 번호에는 하나의 인증 번호 정보만 가지고 있게 된다. 따라서 사용자가 인증번호 발급을 여러 번 눌러 여러 개 발급 받더라고 젤 나중에 발급된 인증 번호로만 인증 가능하다.
@RequiredArgsConstructor
@Repository
public class SmsCertification {
private final String PREFIX = "sms:"; // key값이 중복되지 않도록 상수 선언
private final int LIMIT_TIME = 3 * 60; // 인증번호 유효 시간
private final StringRedisTemplate stringRedisTemplate;
// Redis에 저장
public void createSmsCertification(String phone, String certificationNumber) {
stringRedisTemplate.opsForValue()
.set(PREFIX + phone, certificationNumber, Duration.ofSeconds(LIMIT_TIME));
}
// 휴대전화 번호에 해당하는 인증번호 불러오기
public String getSmsCertification(String phone) {
return stringRedisTemplate.opsForValue().get(PREFIX + phone);
}
// 인증 완료 시, 인증번호 Redis에서 삭제
public void deleteSmsCertification(String phone) {
stringRedisTemplate.delete(PREFIX + phone);
}
// Redis에 해당 휴대번호로 저장된 인증번호가 존재하는지 확인
public boolean hasKey(String phone) {
return stringRedisTemplate.hasKey(PREFIX + phone);
}
}
MessageService.java
아까 1편에서 이미 만들어둔 파일이다 여기에 이제 인증번호 확인 파트도 만들어 줄 것이다.
문자를 발송하고 나서 Redis에 인증번호를 저장하는 부분과
추후 사용자로 부터 인증번호가 넘어오면 Redis에 저장된 값과 같은지 비교를 통해 검증한다.
코드가 길어 1편에 나왔던 부분은 조금 생략했으니 필요하다면 1편을 참조하기를
@Service
@RequiredArgsConstructor
public class MessageService {
...
// 인증번호 전송하기
public String sendSMS(String phonNumber) {
Message coolsms = new Message(apiKey, apiSecret);
// 랜덤한 인증 번호 생성
String randomNum = createRandomNumber();
System.out.println(randomNum);
...
// DB에 발송한 인증번호 저장
smsCertification.createSmsCertification(phonNumber,randomNum);
return "문자 전송이 완료되었습니다.";
}
// 인증 번호 검증
public String verifySms(UserDto.SmsCertificationDto requestDto) {
if (isVerify(requestDto)) {
throw new IllegalArgumentException("인증번호가 일치하지 않습니다.");
}
smsCertification.deleteSmsCertification(requestDto.getPhoneNumber());
return "인증 완료되었습니다.";
}
private boolean isVerify(UserDto.SmsCertificationDto requestDto) {
return !(smsCertification.hasKey(requestDto.getPhoneNumber()) &&
smsCertification.getSmsCertification(requestDto.getPhoneNumber())
.equals(requestDto.getRandomNumber()));
}
}
이렇게 비교적 간단하게 문자 SMS 인증과정을 다 구현해보았다.
생각보다 Spring에서 제공하는 기능이 많고 API가 잘되어있어 구현에 어려움은 없었다.
Reference : https://1-7171771.tistory.com/141