diff --git a/src/main/java/com/kcy/fitapet/domain/member/exception/SmsErrorCode.java b/src/main/java/com/kcy/fitapet/domain/member/exception/SmsErrorCode.java index 70469d3d..41f84794 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/exception/SmsErrorCode.java +++ b/src/main/java/com/kcy/fitapet/domain/member/exception/SmsErrorCode.java @@ -13,7 +13,9 @@ public enum SmsErrorCode implements StatusCode { EXPIRED_AUTH_CODE(BAD_REQUEST, "인증 시간이 만료되었습니다"), INVALID_AUTH_CODE(BAD_REQUEST, "유효하지 않은 인증 코드입니다"), - INVALID_RECEIVER(BAD_REQUEST, "유효하지 않은 수신자입니다"); + INVALID_RECEIVER(BAD_REQUEST, "유효하지 않은 수신자입니다"), + NOT_FOUND_SMS_PREFIX(BAD_REQUEST, "유효하지 않은 인증 타입입니다.") + ; private final HttpStatus httpStatus; private final String message; diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java b/src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java index 5652d663..6e9e8afc 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java @@ -93,15 +93,17 @@ private ResponseEntity getResponseEntity(Jwt jwt) { @PostMapping("/{id}/sms") public ResponseEntity signUpSmsAuthorization( + @PathVariable("id") Long id, + @RequestParam("provider") ProviderType provider, @RequestParam(value = "code", required = false) String code, @RequestBody @Valid SmsReq req ) { if (code == null) { - SmsRes smsRes = memberAuthService.sendCode(req, SmsPrefix.OAUTH); + SmsRes smsRes = oAuthService.sendCode(req, id, provider, SmsPrefix.OAUTH); return ResponseEntity.ok(SuccessResponse.from(smsRes)); } - String token = oAuthService.checkCertificationNumber(req, code); + String token = oAuthService.checkCertificationNumber(req, id, code); if (!StringUtils.hasText(token)) return ResponseEntity.status(HttpStatus.SC_UNAUTHORIZED).build(); diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java b/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java index 679ddc1c..d2fd5df6 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java @@ -26,12 +26,16 @@ import com.kcy.fitapet.global.common.security.oauth.dto.OIDCDecodePayload; import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKeyResponse; import com.kcy.fitapet.global.common.util.sms.SmsProvider; +import com.kcy.fitapet.global.common.util.sms.dto.SensInfo; import com.kcy.fitapet.global.common.util.sms.dto.SmsReq; +import com.kcy.fitapet.global.common.util.sms.dto.SmsRes; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor @Slf4j @@ -81,13 +85,23 @@ public Jwt signUpByOIDC(Long id, ProviderType provider, OauthSignUpReq req) { } @Transactional - public String checkCertificationNumber(SmsReq req, String code) { + public SmsRes sendCode(SmsReq dto, Long id, ProviderType provider, SmsPrefix prefix) { + SensInfo smsInfo = smsProvider.sendCodeByPhoneNumber(dto); + + smsCertificationService.saveSmsAuthToken(dto.to(), smsInfo.code(), prefix); + LocalDateTime expireTime = smsCertificationService.getExpiredTime(dto.to(), prefix); + log.info("인증번호 만료 시간: {}", expireTime); + return SmsRes.of(dto.to(), smsInfo.requestTime(), expireTime); + } + + @Transactional + public String checkCertificationNumber(SmsReq req, Long id, String code) { if (!smsCertificationService.isCorrectCode(req.to(), code, SmsPrefix.REGISTER)) { log.warn("인증번호 불일치 -> 사용자 입력 인증 번호 : {}", code); throw new GlobalErrorException(SmsErrorCode.INVALID_AUTH_CODE); } - String token = jwtUtil.generateSmsOauthToken(SmsAuthInfo.of(1L, req.to())); + String token = jwtUtil.generateSmsOauthToken(SmsAuthInfo.of(id, req.to())); smsCertificationService.saveSmsAuthToken(req.to(), token, SmsPrefix.OAUTH); return token; diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationService.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationService.java index 08b23549..9c778f7f 100644 --- a/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationService.java +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationService.java @@ -5,10 +5,10 @@ public interface SmsCertificationService { /** * SMS 인증 완료 후 계정 생성을 위한 토큰 저장 - * @param phoneNumber : String - * @param key : String + * @param phone : String + * @param code : String */ - void saveSmsAuthToken(String phoneNumber, String key, SmsPrefix prefix); + void saveSmsAuthToken(String phone, String code, SmsPrefix prefix); /** * 인증번호 확인 diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationServiceImpl.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationServiceImpl.java index 7562c90e..13d28558 100644 --- a/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationServiceImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationServiceImpl.java @@ -1,6 +1,8 @@ package com.kcy.fitapet.global.common.redis.sms; import com.kcy.fitapet.domain.member.exception.SmsErrorCode; +import com.kcy.fitapet.global.common.redis.sms.dao.*; +import com.kcy.fitapet.global.common.redis.sms.domain.*; import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -14,34 +16,71 @@ @Service @RequiredArgsConstructor public class SmsCertificationServiceImpl implements SmsCertificationService { - private final SmsCertificationRepository smsCertificationRepository; + private final SmsOauthRepository smsOauthRepository; + private final SmsPasswordRepository smsPasswordRepository; + private final SmsRegisterRepository smsRegisterRepository; + private final SmsUidRepository smsUidRepository; + private final RedisTemplate redisTemplate; @Override - public void saveSmsAuthToken(String phoneNumber, String value, SmsPrefix prefix) { - smsCertificationRepository.save(SmsCertification.of(prefix.getTopic(phoneNumber), value)); + public void saveSmsAuthToken(String phone, String code, SmsPrefix prefix) { + switch (prefix) { + case OAUTH -> smsOauthRepository.save(SmsOauth.of(phone, code)); + case PASSWORD -> smsPasswordRepository.save(SmsPassword.of(phone, code)); + case REGISTER -> smsRegisterRepository.save(SmsRegister.of(phone, code)); + case UID -> smsUidRepository.save(SmsUid.of(phone, code)); + } } @Override public boolean isCorrectCode(String phoneNumber, String code, SmsPrefix prefix) { - Optional smsCertification = smsCertificationRepository.findById(prefix.getTopic(phoneNumber)); - - return smsCertification.map(certification -> certification.getCertificationNumber().equals(code)).orElse(false); + switch (prefix) { + case OAUTH -> { + Optional smsOauth = smsOauthRepository.findById(phoneNumber); + return smsOauth.map(oauth -> oauth.getCode().equals(code)).orElse(false); + } + case PASSWORD -> { + Optional smsPassword = smsPasswordRepository.findById(phoneNumber); + return smsPassword.map(password -> password.getCode().equals(code)).orElse(false); + } + case REGISTER -> { + Optional smsRegister = smsRegisterRepository.findById(phoneNumber); + return smsRegister.map(register -> register.getCode().equals(code)).orElse(false); + } + case UID -> { + Optional smsUid = smsUidRepository.findById(phoneNumber); + return smsUid.map(uid -> uid.getCode().equals(code)).orElse(false); + } + default -> throw new GlobalErrorException(SmsErrorCode.NOT_FOUND_SMS_PREFIX); + } } @Override public boolean existsCode(String phoneNumber, SmsPrefix prefix) { - return smsCertificationRepository.existsById(prefix.getTopic(phoneNumber)); + switch (prefix) { + case OAUTH -> {return smsOauthRepository.existsById(phoneNumber);} + case PASSWORD -> {return smsPasswordRepository.existsById(phoneNumber);} + case REGISTER -> {return smsRegisterRepository.existsById(phoneNumber);} + case UID -> {return smsUidRepository.existsById(phoneNumber);} + default -> throw new GlobalErrorException(SmsErrorCode.NOT_FOUND_SMS_PREFIX); + } } @Override public void removeCode(String phoneNumber, SmsPrefix prefix) { - smsCertificationRepository.deleteById(prefix.getTopic(phoneNumber)); + switch (prefix) { + case OAUTH -> {smsOauthRepository.deleteById(phoneNumber);} + case PASSWORD -> {smsPasswordRepository.deleteById(phoneNumber);} + case REGISTER -> {smsRegisterRepository.deleteById(phoneNumber);} + case UID -> {smsUidRepository.deleteById(phoneNumber);} + default -> throw new GlobalErrorException(SmsErrorCode.NOT_FOUND_SMS_PREFIX); + } } @Override public LocalDateTime getExpiredTime(String phoneNumber, SmsPrefix prefix) { - Long ttl = redisTemplate.getExpire("smsCertification:" + prefix.getTopic(phoneNumber)); + Long ttl = redisTemplate.getExpire(getKeyName(phoneNumber, prefix)); log.info("ttl: {}", ttl); if (ttl == null || ttl < 0L) @@ -49,4 +88,10 @@ public LocalDateTime getExpiredTime(String phoneNumber, SmsPrefix prefix) { return LocalDateTime.now().plusSeconds(ttl); } + + private String getKeyName(String phoneNumber, SmsPrefix prefix) { + String str = prefix.getPrefix(); + return "sms" + str.substring(0, 1).toUpperCase() + + str.substring(1) + ":" + phoneNumber; + } } diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationRepository.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsCertificationRepository.java similarity index 55% rename from src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationRepository.java rename to src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsCertificationRepository.java index 672649aa..7b963a2e 100644 --- a/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertificationRepository.java +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsCertificationRepository.java @@ -1,5 +1,6 @@ -package com.kcy.fitapet.global.common.redis.sms; +package com.kcy.fitapet.global.common.redis.sms.dao; +import com.kcy.fitapet.global.common.redis.sms.domain.SmsCertification; import org.springframework.data.repository.CrudRepository; public interface SmsCertificationRepository extends CrudRepository { diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsOauthRepository.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsOauthRepository.java new file mode 100644 index 00000000..efd0288d --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsOauthRepository.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.global.common.redis.sms.dao; + +import com.kcy.fitapet.global.common.redis.sms.domain.SmsOauth; +import org.springframework.data.repository.CrudRepository; + +public interface SmsOauthRepository extends CrudRepository { +} diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsPasswordRepository.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsPasswordRepository.java new file mode 100644 index 00000000..4d32e942 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsPasswordRepository.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.global.common.redis.sms.dao; + +import com.kcy.fitapet.global.common.redis.sms.domain.SmsPassword; +import org.springframework.data.repository.CrudRepository; + +public interface SmsPasswordRepository extends CrudRepository { +} diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsRegisterRepository.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsRegisterRepository.java new file mode 100644 index 00000000..e640a392 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsRegisterRepository.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.global.common.redis.sms.dao; + +import com.kcy.fitapet.global.common.redis.sms.domain.SmsRegister; +import org.springframework.data.repository.CrudRepository; + +public interface SmsRegisterRepository extends CrudRepository { +} diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsUidRepository.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsUidRepository.java new file mode 100644 index 00000000..3683c3f9 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/dao/SmsUidRepository.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.global.common.redis.sms.dao; + +import com.kcy.fitapet.global.common.redis.sms.domain.SmsUid; +import org.springframework.data.repository.CrudRepository; + +public interface SmsUidRepository extends CrudRepository { +} diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertification.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsCertification.java similarity index 63% rename from src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertification.java rename to src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsCertification.java index 95a52a83..161463dc 100644 --- a/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsCertification.java +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsCertification.java @@ -1,4 +1,4 @@ -package com.kcy.fitapet.global.common.redis.sms; +package com.kcy.fitapet.global.common.redis.sms.domain; import lombok.Builder; import lombok.Getter; @@ -10,18 +10,18 @@ public class SmsCertification { @Id private final String phoneNumber; - private final String certificationNumber; + private final String code; @Builder - public SmsCertification(String phoneNumber, String certificationNumber) { + public SmsCertification(String phoneNumber, String code) { this.phoneNumber = phoneNumber; - this.certificationNumber = certificationNumber; + this.code = code; } - public static SmsCertification of(String phoneNumber, String certificationNumber) { + public static SmsCertification of(String phoneNumber, String code) { return SmsCertification.builder() .phoneNumber(phoneNumber) - .certificationNumber(certificationNumber) + .code(code) .build(); } } diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsOauth.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsOauth.java new file mode 100644 index 00000000..489c5dc9 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsOauth.java @@ -0,0 +1,27 @@ +package com.kcy.fitapet.global.common.redis.sms.domain; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +@RedisHash(value = "smsOauth", timeToLive = 300) +@Getter +public class SmsOauth { + @Id + private final String phoneNumber; + private final String code; + + @Builder + public SmsOauth(String phoneNumber, String code) { + this.phoneNumber = phoneNumber; + this.code = code; + } + + public static SmsOauth of(String phoneNumber, String code) { + return SmsOauth.builder() + .phoneNumber(phoneNumber) + .code(code) + .build(); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsPassword.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsPassword.java new file mode 100644 index 00000000..f164acae --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsPassword.java @@ -0,0 +1,27 @@ +package com.kcy.fitapet.global.common.redis.sms.domain; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +@RedisHash(value = "smsPassword", timeToLive = 300) +@Getter +public class SmsPassword { + @Id + private final String phoneNumber; + private final String code; + + @Builder + public SmsPassword(String phoneNumber, String code) { + this.phoneNumber = phoneNumber; + this.code = code; + } + + public static SmsPassword of(String phoneNumber, String code) { + return SmsPassword.builder() + .phoneNumber(phoneNumber) + .code(code) + .build(); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsRegister.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsRegister.java new file mode 100644 index 00000000..fa934ce8 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsRegister.java @@ -0,0 +1,27 @@ +package com.kcy.fitapet.global.common.redis.sms.domain; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +@RedisHash(value = "smsRegister", timeToLive = 300) +@Getter +public class SmsRegister { + @Id + private final String phoneNumber; + private final String code; + + @Builder + public SmsRegister(String phoneNumber, String code) { + this.phoneNumber = phoneNumber; + this.code = code; + } + + public static SmsRegister of(String phoneNumber, String code) { + return SmsRegister.builder() + .phoneNumber(phoneNumber) + .code(code) + .build(); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsUid.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsUid.java new file mode 100644 index 00000000..ab449686 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/domain/SmsUid.java @@ -0,0 +1,27 @@ +package com.kcy.fitapet.global.common.redis.sms.domain; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +@RedisHash(value = "smsUid", timeToLive = 300) +@Getter +public class SmsUid { + @Id + private final String phoneNumber; + private final String code; + + @Builder + public SmsUid(String phoneNumber, String code) { + this.phoneNumber = phoneNumber; + this.code = code; + } + + public static SmsUid of(String phoneNumber, String code) { + return SmsUid.builder() + .phoneNumber(phoneNumber) + .code(code) + .build(); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/config/RedisConfig.java b/src/main/java/com/kcy/fitapet/global/config/RedisConfig.java index 6d69b3a3..b6f040ee 100644 --- a/src/main/java/com/kcy/fitapet/global/config/RedisConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/RedisConfig.java @@ -1,6 +1,6 @@ package com.kcy.fitapet.global.config; -import com.kcy.fitapet.global.common.redis.sms.SmsCertification; +import com.kcy.fitapet.global.common.redis.sms.domain.SmsCertification; import com.kcy.fitapet.global.config.feign.OidcCacheManager; import com.kcy.fitapet.global.config.feign.RedisCacheConnectionFactory; import org.springframework.beans.factory.annotation.Value;