티스토리 뷰
이전 글에서 SMS 본인 인증(엄밀히 말하면 휴대폰 인증)을 구현하는 법을 간단히 알아봤다.
하지만 문제는, 반복 요청에 대한 처리를 하지 않았는데 이 외에도 몇 가지 문제가 더 있다.
1. 이미 인증한 사람이 인증 번호 반복 요청
2. 아직 인증하지 않은 사람의 인증 번호 반복 요청
3. 인증한 사람이 다른 번호로 인증 번호 반복 요청
인증 번호를 요청하는 것은 단순하게 생각하면 FE에서 막아줄 수 있다.
문제는 새로고침 했을 때, 이 사람이 인증을 했거나 대기 중인 상태란 걸 내려 줄 수 있어야 한다.
(그렇지 않으면 당연히 반복 요청을 할 수 있을 것이다)
방법은 여러가지가 있을 것 같다.
user 테이블에서 sms_cert 와 같은 컬럼을 생성하고 , "Y", "N", "P"(pending) 값을 갖게하는 관리법이 가장 먼저 떠오른다.
문제는 현재 user 테이블의 역할이 너무 커지고 있어서 위 내용을 분리하고 싶었다.
그래서 별도의 테이블을 생성했다.
CREATE TABLE `tb_sms_verification` (
`user_id` varchar(64) NOT NULL COMMENT '유저 id',
`phone_number` varchar(64) NOT NULL COMMENT '핸드폰 번호',
`verification_key` varchar(64) DEFAULT NULL COMMENT '인증 여부 상태 확인 코드',
`verification` char(1) DEFAULT 'N',
`reg_date` datetime NOT NULL COMMENT '인증 번호 요청 시간',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
첫 인증 요청을 하게되면 유저 id를 키값으로 하는 row가 테이블에 추가되게 된다.
(본 포스팅에서는 이 부분까지 설명하지는 않을 것이다.)
그리고 생각해보면 딱히 pending 상태를 관리할 필요가 없다.
컬럼이 생성된 상태면 이미 한번은 인증 번호를 요청한 것이고,
reg_date가 현재 시간 기준 -10분 보다 크면 대기 상태임을 알 수 있기 때문이다.
이렇게 테이블을 생성하면 많은 것을 처리할 수 있다.
하나씩 해보자.
1. 아직 인증하지 않은 사람의 반복 요청
두 가지 방법이 있는데, 첫번째는 Twilio에 API로 상태 요청을 통해 확인하는 방법이다.
ResultVo<String> resultVo = new ResultVo<>();
SmsVerification smsVerification = smsVerificationRepository.findFirstByPhoneNumberOrderByRegDateDesc(phoneNumber);
// 동일한 유저가 같은 번호로 반복 요청했을 때
// 익셉션이 발생하지 않는다 -> pending 상태
Verification.fetcher(serviceSid,
smsVerification.getVerificationKey())
.fetch();
resultVo.setCode("20022");
return new ResponseEntity<>(resultVo, HttpStatus.ACCEPTED);
이 방법의 짚고 넘어가야하는 부분은 인증 번호를 요청하면 VE로 시작하는 VerificationKey를 주고, 이를 이용해야 한다.
ResultVo라는 공통 객체에 FE와 사전 정의한 인증 대기상태 코드를 실어서 내려보내줬다.
문제는 이 방법은 같은 유저가 다른 번호로 인증 번호를 요청했을 때 막을 수가 없었다.
그래서 인증 요청 정보를 갖고 있는 테이블 조회로 userId와 10분 이내의 인증 여부로 중복을 체크했다.
LocalDateTime tenMinutesAgo = LocalDateTime.now().minusMinutes(10);
SmsVerification timeVerification = smsVerificationRepository.findFirstByUserIdAndVerificationAndRegDateAfter(userId, "N", tenMinutesAgo);
if(timeVerification != null) {
resultVo.setCode("20022");
return new ResponseEntity<>(resultVo, HttpStatus.ACCEPTED);
}
이렇게 하면 3번인 이미 인증한 사람이 다른 번호로 반복 요청하는 것도 함께 막을 수 있다.
2. 이미 인증한 사람이 인증 번호 반복 요청
SmsVerification smsVerification = smsVerificationRepository.findFirstByPhoneNumberOrderByRegDateDesc(phoneNumber);
if (smsVerification != null) {
if("Y".equals(smsVerification.getVerification())) {
resultVo.setCode("20002");
return new ResponseEntity<>(resultVo, HttpStatus.ACCEPTED);
}
}
이번 것도 DB를 체크하면 간단하다.
사용자가 인증 번호를 한번이라도 요청하면 DB에 값이 저장되도록 구현되어 있다.
이미 검증을 했다면, 마지막으로 저장된 데이터는 인증 여부의 값이 "Y"일 것이다.
이 정보를 가지고 응답 값을 내려줬다.
인증번호 검증의 문제
사실 검증과정도 문제가 있긴하다. 그러나 검증에 대해선 추가 요금을 발생시키지는 않는다.
1. 이미 인증한 사람의 인증 번호 검증 반복 요청
2. 인증 번호를 받은 사람이 잘못된 인증 번호로 검증 반복 요청
1번은 인증 번호 만료 처리
2번은 인증 번호가 잘못되었다는 안내를 해줘야 한다.
try {
VerificationCheck verification = VerificationCheck.creator(serviceSid)
.setCode(userVerifyCheckRequestDto.getCode())
.setTo(phone)
.create();
if("approved".equals(verification.getStatus())) {
// ... 인증 성공 했을 때의 처리
}
// 유저가 인증 번호를 잘못 보냈을 때
throw new BadRequestException("잘못된 인증 코드");
} catch (ApiException exception) {
resultVo.setCode("20016");
return new ResponseEntity<>(resultVo, HttpStatus.PARTIAL_CONTENT);
}
한번에 정리했는데, 신기한 점은 인증코드를 아예 잘못된걸 보냈을 때는 익셉션이 발생하지 않아서 try-catch 구문 안에서 익셉션을 던지도록 처리했다.
익셉션이 발생하는 경우는 인증 코드가 만료 되었을 때이다.
마치며
생각해보면 근본적으로 어뷰징을 완전히 막을 수 있는 방법은 없는 것 같다.
예를 들어 Twilio에서는 10분의 텀을 두고 무한 반복 요청을 하면 답이 없다.
(생각해보면 이건 정책적으로 하루에 몇번 막아두면 될 것 같긴하다... 방금 생각났으니 추가해야할듯)
그리고 계정을 여러개 만들어두고 부모가족친지 번호로 인증을 해버리면 막을 수 있는 방법이 없다.
지금 만들어 놓은 것도 분명히 구멍이 있을테니 꾸준히 모니터링을 해야한다..
'개발 > SPRING' 카테고리의 다른 글
[Spring/Java] Rollback과 Exception, 그리고 @Transactional (1) | 2023.12.30 |
---|---|
스프링부트에서 kakao oauth 2.0 로그인 구현하기 - CSR(클라이언트 사이드 랜더링) 기반 (0) | 2023.11.02 |
스프링부트에서 SMS 본인 인증 구현하기 - 1. Twilio로 구현 (1) | 2023.10.24 |
스프링부트에서 JWT 적용하기 - 3. 필터(filter)에 적용 (0) | 2023.10.10 |
스프링부트에서 JWT 적용하기 - 2. 인터셉터(Interceptor)에 적용 (1) | 2023.10.06 |
- Total
- Today
- Yesterday
- Spring
- lambda
- cache
- AWS EC2
- CloudFront
- 람다
- Log
- openAI API
- JWT
- docker
- serverless
- springboot
- 티스토리챌린지
- AWS
- EKS
- GIT
- 후쿠오카
- OpenFeign
- MySQL
- terraform
- S3
- OpenAI
- Kotlin
- 스프링부트
- AOP
- java
- elasticsearch
- ChatGPT
- Elastic cloud
- 오블완
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |