-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #45 from tukcomCD2024/ARCH-114-feat/message-verifi…
…cation feat: 문자 인증 기능 추가
- Loading branch information
Showing
28 changed files
with
564 additions
and
38 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
.../core/domain/auth/service/MemberInfo.java → ...hive/core/domain/auth/dto/MemberInfo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 9 additions & 4 deletions
13
.../site/timecapsulearchive/core/domain/auth/dto/request/VerificationMessageSendRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,19 @@ | ||
package site.timecapsulearchive.core.domain.auth.dto.request; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.Pattern; | ||
import jakarta.validation.constraints.NotBlank; | ||
import site.timecapsulearchive.core.global.common.valid.annotation.Phone; | ||
|
||
@Schema(description = "인증 문자 요청") | ||
public record VerificationMessageSendRequest( | ||
|
||
@Schema(description = "핸드폰 번호") | ||
@Pattern(regexp = "^01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$", message = "10 ~ 11 자리의 숫자만 입력 가능합니다.") | ||
String phone | ||
@Schema(description = "수신자 핸드폰 번호 ex)01012341234") | ||
@Phone | ||
String receiver, | ||
|
||
@Schema(description = "앱의 해시 키") | ||
@NotBlank(message = "앱의 해시 키는 필수입니다.") | ||
String appHashKey | ||
) { | ||
|
||
} |
19 changes: 19 additions & 0 deletions
19
...ite/timecapsulearchive/core/domain/auth/dto/response/VerificationMessageSendResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package site.timecapsulearchive.core.domain.auth.dto.response; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
@Schema(name = "인증 문자 발송 응답") | ||
public record VerificationMessageSendResponse( | ||
|
||
@Schema(name = "전송 상태") | ||
Integer status, | ||
|
||
@Schema(name = "상태 메시지") | ||
String message | ||
) { | ||
|
||
public static VerificationMessageSendResponse success(final Integer status, | ||
final String message) { | ||
return new VerificationMessageSendResponse(status, message); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...main/java/site/timecapsulearchive/core/domain/auth/exception/TooManyRequestException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package site.timecapsulearchive.core.domain.auth.exception; | ||
|
||
import site.timecapsulearchive.core.global.error.ErrorCode; | ||
import site.timecapsulearchive.core.global.error.exception.BusinessException; | ||
|
||
public class TooManyRequestException extends BusinessException { | ||
|
||
public TooManyRequestException() { | ||
super(ErrorCode.TOO_MANY_REQUEST_ERROR); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
.../timecapsulearchive/core/domain/auth/repository/MessageAuthenticationCacheRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package site.timecapsulearchive.core.domain.auth.repository; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.data.redis.core.StringRedisTemplate; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
@RequiredArgsConstructor | ||
public class MessageAuthenticationCacheRepository { | ||
|
||
private static final int MINUTE = 5; | ||
private static final String PREFIX = "messageAuthentication:"; | ||
|
||
private final StringRedisTemplate redisTemplate; | ||
|
||
public void save(final Long memberId, final String code) { | ||
redisTemplate.opsForValue().set(PREFIX + memberId, code, MINUTE, TimeUnit.MINUTES); | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
...ain/java/site/timecapsulearchive/core/domain/auth/service/MessageVerificationService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package site.timecapsulearchive.core.domain.auth.service; | ||
|
||
import java.util.concurrent.ThreadLocalRandom; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import site.timecapsulearchive.core.domain.auth.dto.response.VerificationMessageSendResponse; | ||
import site.timecapsulearchive.core.domain.auth.repository.MessageAuthenticationCacheRepository; | ||
import site.timecapsulearchive.core.infra.sms.SmsApiService; | ||
import site.timecapsulearchive.core.infra.sms.dto.SmsApiResponse; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class MessageVerificationService { | ||
|
||
private static final int MIN = 1000; | ||
private static final int MAX = 10000; | ||
|
||
private final MessageAuthenticationCacheRepository messageAuthenticationCacheRepository; | ||
private final SmsApiService smsApiService; | ||
|
||
/** | ||
* 사용자 아이디와 수신자 핸드폰을 받아서 인증번호를 발송한다. | ||
* | ||
* @param memberId 사용자 아이디 | ||
* @param receiver 수신자 핸드폰 번호 | ||
* @param appHashKey 앱의 해시 키(메시지 자동 파싱) | ||
*/ | ||
public VerificationMessageSendResponse sendVerificationMessage( | ||
final Long memberId, | ||
final String receiver, | ||
final String appHashKey | ||
) { | ||
final String code = generateRandomCode(); | ||
|
||
final String message = generateMessage(code, appHashKey); | ||
|
||
final SmsApiResponse apiResponse = smsApiService.sendMessage(receiver, message); | ||
|
||
messageAuthenticationCacheRepository.save(memberId, code); | ||
|
||
return VerificationMessageSendResponse.success(apiResponse.resultCode(), | ||
apiResponse.message()); | ||
} | ||
|
||
private String generateMessage(final String code, final String appHashKey) { | ||
return "<#>[ARchive]" | ||
+ "본인확인 인증번호는 [" | ||
+ code | ||
+ "]입니다." | ||
+ appHashKey; | ||
} | ||
|
||
private String generateRandomCode() { | ||
return String.valueOf( | ||
ThreadLocalRandom.current() | ||
.nextInt(MIN, MAX) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
.../core/src/main/java/site/timecapsulearchive/core/global/api/ApiLimitCheckInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package site.timecapsulearchive.core.global.api; | ||
|
||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.servlet.HandlerInterceptor; | ||
import site.timecapsulearchive.core.domain.auth.exception.TooManyRequestException; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class ApiLimitCheckInterceptor implements HandlerInterceptor { | ||
|
||
private static final int MAX_API_CALL_LIMIT = 5; | ||
private static final int NO_USAGE = 0; | ||
|
||
private final ApiUsageCacheRepository apiUsageCacheRepository; | ||
|
||
/** | ||
* 엔드포인트에 Api 요청 횟수를 검사하는 인터셉터이다. | ||
* WebMvcConfig에 path로 등록된 경로는 여기를 거치게 된다. | ||
* 아직 문자 인증에 대한 요청만 걸려있다. | ||
* @param request 요청 | ||
* @param response 응답 | ||
* @param handler 해당 요청을 처리할 메서드 | ||
* @return 클라이언트가 보낸 요청이 Api 횟수 제한에 걸리는지 여부 {@code True, False} | ||
* @throws TooManyRequestException 횟수 제한이 발생하면 예외 발생 | ||
*/ | ||
@Override | ||
public boolean preHandle( | ||
HttpServletRequest request, | ||
HttpServletResponse response, | ||
Object handler | ||
) throws TooManyRequestException { | ||
Long memberId = (Long) SecurityContextHolder.getContext() | ||
.getAuthentication() | ||
.getPrincipal(); | ||
|
||
Integer apiUsageCount = apiUsageCacheRepository.getSmsApiUsage(memberId) | ||
.orElse(NO_USAGE); | ||
|
||
if (apiUsageCount > MAX_API_CALL_LIMIT) { | ||
throw new TooManyRequestException(); | ||
} | ||
|
||
if (isFirstRequest(apiUsageCount)) { | ||
apiUsageCacheRepository.saveAsFirstRequest(memberId); | ||
return true; | ||
} | ||
|
||
apiUsageCacheRepository.increaseSmsApiUsage(memberId); | ||
|
||
return true; | ||
} | ||
|
||
private boolean isFirstRequest(Integer apiUsageCount) { | ||
return apiUsageCount.equals(NO_USAGE); | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
...d/core/src/main/java/site/timecapsulearchive/core/global/api/ApiUsageCacheRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package site.timecapsulearchive.core.global.api; | ||
|
||
import java.util.Optional; | ||
import java.util.concurrent.TimeUnit; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.data.redis.core.StringRedisTemplate; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
@RequiredArgsConstructor | ||
public class ApiUsageCacheRepository { | ||
|
||
private static final String PREFIX = "apiUsage:"; | ||
private static final String SMS_API_USAGE = "smsApi"; | ||
private static final String FIRST_REQUEST = "1"; | ||
private static final int EXPIRATION_DAYS = 1; | ||
|
||
private final StringRedisTemplate redisTemplate; | ||
|
||
public Optional<Integer> getSmsApiUsage(Long memberId) { | ||
String result = (String) redisTemplate.opsForHash().get(PREFIX + memberId, SMS_API_USAGE); | ||
|
||
if (result == null) { | ||
return Optional.empty(); | ||
} | ||
|
||
return Optional.of(Integer.parseInt(result)); | ||
} | ||
|
||
public void increaseSmsApiUsage(Long memberId) { | ||
redisTemplate.opsForHash().increment(PREFIX + memberId, SMS_API_USAGE, 1); | ||
} | ||
|
||
public void saveAsFirstRequest(Long memberId) { | ||
String key = PREFIX + memberId; | ||
|
||
redisTemplate.opsForHash().put(key, SMS_API_USAGE, FIRST_REQUEST); | ||
|
||
redisTemplate.expire(key, EXPIRATION_DAYS, TimeUnit.DAYS); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
...d/core/src/main/java/site/timecapsulearchive/core/global/common/valid/PhoneValidator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package site.timecapsulearchive.core.global.common.valid; | ||
|
||
import jakarta.validation.ConstraintValidator; | ||
import jakarta.validation.ConstraintValidatorContext; | ||
import java.util.regex.Pattern; | ||
import site.timecapsulearchive.core.global.common.valid.annotation.Phone; | ||
|
||
public class PhoneValidator implements ConstraintValidator<Phone, String> { | ||
|
||
private static final String PHONE_REGEX = "^\\d{11}$"; | ||
|
||
@Override | ||
public boolean isValid(String value, ConstraintValidatorContext context) { | ||
return Pattern.matches(PHONE_REGEX, value); | ||
} | ||
} |
Oops, something went wrong.