From 21a9463394883446ad0ccc95a6e4df98e819eff8 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Sat, 23 Dec 2023 19:48:58 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20#11=20#45=20kakao=20OIDC=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../com/kcy/fitapet/FitapetApplication.java | 2 +- .../fitapet/domain/member/api/AuthApi.java | 10 +-- .../service/component/MemberAuthService.java | 10 +-- .../api/{OAuthApi.java => OauthApi.java} | 11 +-- .../domain/oauth/dao/OAuthRepository.java | 7 -- .../domain/oauth/dao/OauthRepository.java | 13 ++++ .../{OAuthAccount.java => OauthAccount.java} | 15 ++-- .../domain/oauth/dto/OauthSignInReq.java | 4 +- .../oauth/exception/OauthException.java | 25 +++++++ .../domain/oauth/service/OAuthService.java | 34 --------- .../oauth/service/component/OauthService.java | 72 +++++++++++++++++++ .../module/OauthApplicationConfigHelper.java | 25 +++++++ .../service/module/OauthClientHelper.java | 24 +++++++ .../service/module/OauthSearchService.java | 34 +++++++++ .../redis/refresh/RefreshTokenService.java | 2 +- .../refresh/RefreshTokenServiceImpl.java | 8 +-- .../access/AccessTokenInfoResolver.java | 9 ++- .../handler/GlobalExceptionHandler.java | 3 +- .../authentication/CustomUserDetails.java | 2 +- .../filter/JwtAuthenticationFilter.java | 10 +-- .../security/filter/JwtExceptionFilter.java | 6 +- .../{util => security}/jwt/AuthConstants.java | 2 +- .../{util => security}/jwt/JwtUtil.java | 8 +-- .../{util => security}/jwt/JwtUtilImpl.java | 12 ++-- .../global/common/security/jwt/dto/Jwt.java | 10 +++ .../jwt/dto/JwtUserInfo.java | 4 +- .../jwt/dto/SmsAuthInfo.java | 4 +- .../common/security/jwt/entity/JwtDto.java | 4 ++ .../jwt/exception/AuthErrorCode.java | 2 +- .../jwt/exception/AuthErrorException.java | 2 +- .../jwt/exception/AuthErrorResponse.java | 2 +- .../jwt/exception/JwtErrorCodeUtil.java | 4 +- .../oauth/OauthApplicationConfig.java | 11 +++ .../common/security/oauth/OauthClient.java | 7 ++ .../security/oauth/OauthOIDCHelper.java | 22 ++++-- .../security/oauth/OauthOIDCProvider.java | 18 ++++- .../oauth}/OauthOIDCProviderImpl.java | 29 +++++--- .../oauth/kakao/KakaoApplicationConfig.java | 28 ++++++++ .../oauth/kakao/KakaoOauthClient.java | 6 +- .../oauth/kakao/KauthErrorDecoder.java | 11 --- .../kakao/dto/KakaoKauthErrorResponse.java | 32 --------- .../global/common/util/jwt/dto/Jwt.java | 10 --- .../global/common/util/jwt/entity/JwtDto.java | 4 -- .../fitapet/global/config/FeignConfig.java | 2 +- .../global/config/feign/KakaoOauthConfig.java | 11 +-- .../config/security/SecurityFilterConfig.java | 2 +- 47 files changed, 383 insertions(+), 191 deletions(-) rename src/main/java/com/kcy/fitapet/domain/oauth/api/{OAuthApi.java => OauthApi.java} (84%) delete mode 100644 src/main/java/com/kcy/fitapet/domain/oauth/dao/OAuthRepository.java create mode 100644 src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthRepository.java rename src/main/java/com/kcy/fitapet/domain/oauth/domain/{OAuthAccount.java => OauthAccount.java} (78%) create mode 100644 src/main/java/com/kcy/fitapet/domain/oauth/exception/OauthException.java delete mode 100644 src/main/java/com/kcy/fitapet/domain/oauth/service/OAuthService.java create mode 100644 src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java create mode 100644 src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthApplicationConfigHelper.java create mode 100644 src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthClientHelper.java create mode 100644 src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java rename src/main/java/com/kcy/fitapet/global/common/{util => security}/jwt/AuthConstants.java (89%) rename src/main/java/com/kcy/fitapet/global/common/{util => security}/jwt/JwtUtil.java (90%) rename src/main/java/com/kcy/fitapet/global/common/{util => security}/jwt/JwtUtilImpl.java (93%) create mode 100644 src/main/java/com/kcy/fitapet/global/common/security/jwt/dto/Jwt.java rename src/main/java/com/kcy/fitapet/global/common/{util => security}/jwt/dto/JwtUserInfo.java (79%) rename src/main/java/com/kcy/fitapet/global/common/{util => security}/jwt/dto/SmsAuthInfo.java (68%) create mode 100644 src/main/java/com/kcy/fitapet/global/common/security/jwt/entity/JwtDto.java rename src/main/java/com/kcy/fitapet/global/common/{util => security}/jwt/exception/AuthErrorCode.java (97%) rename src/main/java/com/kcy/fitapet/global/common/{util => security}/jwt/exception/AuthErrorException.java (92%) rename src/main/java/com/kcy/fitapet/global/common/{util => security}/jwt/exception/AuthErrorResponse.java (75%) rename src/main/java/com/kcy/fitapet/global/common/{util => security}/jwt/exception/JwtErrorCodeUtil.java (93%) create mode 100644 src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthApplicationConfig.java create mode 100644 src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthClient.java rename src/main/java/com/kcy/fitapet/global/common/{util/jwt => security/oauth}/OauthOIDCProviderImpl.java (78%) create mode 100644 src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoApplicationConfig.java delete mode 100644 src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KauthErrorDecoder.java delete mode 100644 src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/dto/KakaoKauthErrorResponse.java delete mode 100644 src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/Jwt.java delete mode 100644 src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtDto.java diff --git a/build.gradle b/build.gradle index d6bfc728..bef68329 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-parent:2.7.10' implementation 'com.jcraft:jsch:0.1.55' implementation 'org.apache.httpcomponents:httpclient:4.5.14' diff --git a/src/main/java/com/kcy/fitapet/FitapetApplication.java b/src/main/java/com/kcy/fitapet/FitapetApplication.java index affd20da..6887ea22 100644 --- a/src/main/java/com/kcy/fitapet/FitapetApplication.java +++ b/src/main/java/com/kcy/fitapet/FitapetApplication.java @@ -4,13 +4,13 @@ import jakarta.annotation.PostConstruct; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import java.util.TimeZone; @SpringBootApplication @EnableJpaRepositories(repositoryFactoryBeanClass = ExtendedJpaRepositoryFactory.class) - public class FitapetApplication { public static void main(String[] args) { SpringApplication.run(FitapetApplication.class, args); diff --git a/src/main/java/com/kcy/fitapet/domain/member/api/AuthApi.java b/src/main/java/com/kcy/fitapet/domain/member/api/AuthApi.java index 5bfb6c62..93e2517b 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/api/AuthApi.java +++ b/src/main/java/com/kcy/fitapet/domain/member/api/AuthApi.java @@ -15,9 +15,9 @@ import com.kcy.fitapet.global.common.response.code.ErrorCode; import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; import com.kcy.fitapet.global.common.util.cookie.CookieUtil; -import com.kcy.fitapet.global.common.util.jwt.AuthConstants; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.security.jwt.AuthConstants; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorCode; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorException; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; @@ -42,8 +42,8 @@ import java.util.Map; -import static com.kcy.fitapet.global.common.util.jwt.AuthConstants.ACCESS_TOKEN; -import static com.kcy.fitapet.global.common.util.jwt.AuthConstants.REFRESH_TOKEN; +import static com.kcy.fitapet.global.common.security.jwt.AuthConstants.ACCESS_TOKEN; +import static com.kcy.fitapet.global.common.security.jwt.AuthConstants.REFRESH_TOKEN; @Tag(name = "유저 관리 API", description = "유저 인증과 관련된 API") @RestController diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java index 43763f01..ba941cc5 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java @@ -11,9 +11,9 @@ import com.kcy.fitapet.global.common.response.code.ErrorCode; import com.kcy.fitapet.global.common.response.code.StatusCode; import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; -import com.kcy.fitapet.global.common.util.jwt.JwtUtil; -import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; -import com.kcy.fitapet.global.common.util.jwt.dto.SmsAuthInfo; +import com.kcy.fitapet.global.common.security.jwt.JwtUtil; +import com.kcy.fitapet.global.common.security.jwt.dto.JwtUserInfo; +import com.kcy.fitapet.global.common.security.jwt.dto.SmsAuthInfo; import com.kcy.fitapet.global.common.redis.forbidden.ForbiddenTokenService; import com.kcy.fitapet.global.common.redis.refresh.RefreshToken; import com.kcy.fitapet.global.common.redis.refresh.RefreshTokenService; @@ -33,8 +33,8 @@ import java.time.LocalDateTime; import java.util.Map; -import static com.kcy.fitapet.global.common.util.jwt.AuthConstants.ACCESS_TOKEN; -import static com.kcy.fitapet.global.common.util.jwt.AuthConstants.REFRESH_TOKEN; +import static com.kcy.fitapet.global.common.security.jwt.AuthConstants.ACCESS_TOKEN; +import static com.kcy.fitapet.global.common.security.jwt.AuthConstants.REFRESH_TOKEN; @Slf4j @Service 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 similarity index 84% rename from src/main/java/com/kcy/fitapet/domain/oauth/api/OAuthApi.java rename to src/main/java/com/kcy/fitapet/domain/oauth/api/OauthApi.java index c389e8cf..6c78d31a 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 @@ -1,9 +1,8 @@ package com.kcy.fitapet.domain.oauth.api; -import com.kcy.fitapet.domain.member.dto.auth.SignInReq; import com.kcy.fitapet.domain.oauth.dto.OauthSignInReq; import com.kcy.fitapet.domain.oauth.dto.OauthSignUpReq; -import com.kcy.fitapet.domain.oauth.service.OAuthService; +import com.kcy.fitapet.domain.oauth.service.component.OauthService; import com.kcy.fitapet.domain.oauth.type.ProviderType; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -17,8 +16,8 @@ @RequiredArgsConstructor @RequestMapping("/api/v1/auth/oauth") @Slf4j -public class OAuthApi { - private final OAuthService oAuthService; +public class OauthApi { + private final OauthService oAuthService; @PostMapping("") @PreAuthorize("isAnonymous()") @@ -40,6 +39,10 @@ public void signUp( @RequestParam("provider") ProviderType provider, @RequestBody @Valid OauthSignUpReq req ) { + if (ProviderType.NAVER.equals(provider)) { + + } else { + } } } diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/dao/OAuthRepository.java b/src/main/java/com/kcy/fitapet/domain/oauth/dao/OAuthRepository.java deleted file mode 100644 index 0e048bf2..00000000 --- a/src/main/java/com/kcy/fitapet/domain/oauth/dao/OAuthRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.kcy.fitapet.domain.oauth.dao; - -import com.kcy.fitapet.domain.oauth.api.OAuthApi; -import com.kcy.fitapet.global.common.repository.ExtendedRepository; - -public interface OAuthRepository extends ExtendedRepository { -} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthRepository.java b/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthRepository.java new file mode 100644 index 00000000..57755913 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthRepository.java @@ -0,0 +1,13 @@ +package com.kcy.fitapet.domain.oauth.dao; + +import com.kcy.fitapet.domain.oauth.domain.OauthAccount; +import com.kcy.fitapet.domain.oauth.type.ProviderType; +import com.kcy.fitapet.global.common.repository.ExtendedRepository; + +import java.util.Optional; + +public interface OauthRepository extends ExtendedRepository { + Optional findByOauthIdAndProvider(Long oauthId, ProviderType provider); + boolean existsByOauthIdAndProvider(Long oauthId, ProviderType provider); + boolean existsByEmail(String email); +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/domain/OAuthAccount.java b/src/main/java/com/kcy/fitapet/domain/oauth/domain/OauthAccount.java similarity index 78% rename from src/main/java/com/kcy/fitapet/domain/oauth/domain/OAuthAccount.java rename to src/main/java/com/kcy/fitapet/domain/oauth/domain/OauthAccount.java index df953a56..95a44019 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/domain/OAuthAccount.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/domain/OauthAccount.java @@ -8,15 +8,16 @@ import lombok.*; @Entity +@Getter @Table(name = "OAUTH") @NoArgsConstructor(access = AccessLevel.PROTECTED) @ToString(of = {"id", "provider"}) -public class OAuthAccount extends Auditable { +public class OauthAccount extends Auditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "oauth_id") - private Long OAuthID; + private Long OauthId; @Convert(converter = ProviderTypeConverter.class) private ProviderType provider; private String email; @@ -26,17 +27,17 @@ public class OAuthAccount extends Auditable { private Member member; @Builder - public OAuthAccount(Long id, Long OAuthID, ProviderType provider, String email, Member member) { + public OauthAccount(Long id, Long OauthId, ProviderType provider, String email, Member member) { this.id = id; - this.OAuthID = OAuthID; + this.OauthId = OauthId; this.provider = provider; this.email = email; this.member = member; } - public static OAuthAccount of(Long OAuthID, ProviderType provider, String email, Member member) { - return OAuthAccount.builder() - .OAuthID(OAuthID) + public static OauthAccount of(Long OauthId, ProviderType provider, String email, Member member) { + return OauthAccount.builder() + .OauthId(OauthId) .provider(provider) .email(email) .member(member) diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignInReq.java b/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignInReq.java index 8aaf6f69..4d64f1de 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignInReq.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/dto/OauthSignInReq.java @@ -6,6 +6,8 @@ public record OauthSignInReq( @NotEmpty String id, @NotEmpty - String id_token + String id_token, + @NotEmpty + String nonce ) { } diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/exception/OauthException.java b/src/main/java/com/kcy/fitapet/domain/oauth/exception/OauthException.java new file mode 100644 index 00000000..a0dc580d --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/exception/OauthException.java @@ -0,0 +1,25 @@ +package com.kcy.fitapet.domain.oauth.exception; + +import com.kcy.fitapet.global.common.response.code.StatusCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum OauthException implements StatusCode { + /* BAD REQUEST */ + INVALID_PROVIDER(HttpStatus.BAD_REQUEST, "유효하지 않은 제공자입니다."), + + /* FORBIDDEN */ + NOT_FOUND_MEMBER(HttpStatus.FORBIDDEN, "존재하지 않는 회원입니다."); + + private final HttpStatus httpStatus; + private final String message; + + + @Override + public String getName() { + return name(); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/service/OAuthService.java b/src/main/java/com/kcy/fitapet/domain/oauth/service/OAuthService.java deleted file mode 100644 index 95e25b28..00000000 --- a/src/main/java/com/kcy/fitapet/domain/oauth/service/OAuthService.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.kcy.fitapet.domain.oauth.service; - -import com.kcy.fitapet.domain.oauth.dao.OAuthRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Slf4j -public class OAuthService { - private final OAuthRepository oAuthRepository; - - @Transactional - public void signUpByOIDC() { - - } - - @Transactional - public void signInByOIDC() { - - } - - @Transactional - public void signInByCode() { - - } - - @Transactional - public void signUpByCode() { - - } -} 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 new file mode 100644 index 00000000..760880bb --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/service/component/OauthService.java @@ -0,0 +1,72 @@ +package com.kcy.fitapet.domain.oauth.service.component; + +import com.kcy.fitapet.domain.member.domain.Member; +import com.kcy.fitapet.domain.oauth.service.module.OauthApplicationConfigHelper; +import com.kcy.fitapet.domain.oauth.service.module.OauthClientHelper; +import com.kcy.fitapet.domain.oauth.service.module.OauthSearchService; +import com.kcy.fitapet.domain.oauth.type.ProviderType; +import com.kcy.fitapet.global.common.security.jwt.JwtUtil; +import com.kcy.fitapet.global.common.security.jwt.dto.Jwt; +import com.kcy.fitapet.global.common.security.jwt.dto.JwtUserInfo; +import com.kcy.fitapet.global.common.security.oauth.OauthApplicationConfig; +import com.kcy.fitapet.global.common.security.oauth.OauthClient; +import com.kcy.fitapet.global.common.security.oauth.OauthOIDCHelper; +import com.kcy.fitapet.global.common.security.oauth.dto.OIDCDecodePayload; +import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKeyResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class OauthService { + private final OauthSearchService oauthSearchService; + + private final OauthOIDCHelper oauthOIDCHelper; + private final OauthClientHelper oauthClientHelper; + private final OauthApplicationConfigHelper oauthApplicationConfigHelper; + + private final JwtUtil jwtUtil; + + @Transactional + public void signUpByOIDC() { + + } + + @Transactional + public Jwt signInByOIDC(String id, String idToken, ProviderType provider, String nonce) { + OauthClient oauthClient = oauthClientHelper.getOauthClient(provider); + OIDCPublicKeyResponse oidcPublicKeyResponse = oauthClient.getOIDCPublicKey(); + OauthApplicationConfig oauthApplicationConfig = oauthApplicationConfigHelper.getOauthApplicationConfig(provider); + + OIDCDecodePayload payload = oauthOIDCHelper.getPayloadFromIdToken( + idToken, oauthApplicationConfig.getAuthorizationUri(), + oauthApplicationConfig.getClientId(), nonce, oidcPublicKeyResponse); + + if (oauthSearchService.isExistMember(Long.parseLong(payload.sub()), provider)) { + Member member = oauthSearchService.findMemberByOauthIdAndProvider(Long.parseLong(payload.sub()), provider); + return generateToken(JwtUserInfo.from(member)); + } else { + return null; + } + } + + @Transactional + public void signInByCode() { + + } + + @Transactional + public void signUpByCode() { + + } + + private Jwt generateToken(JwtUserInfo jwtUserInfo) { + return Jwt.builder() + .accessToken(jwtUtil.generateAccessToken(jwtUserInfo)) + .refreshToken(jwtUtil.generateRefreshToken(jwtUserInfo)) + .build(); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthApplicationConfigHelper.java b/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthApplicationConfigHelper.java new file mode 100644 index 00000000..0d24f4cd --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthApplicationConfigHelper.java @@ -0,0 +1,25 @@ +package com.kcy.fitapet.domain.oauth.service.module; + +import com.kcy.fitapet.domain.oauth.exception.OauthException; +import com.kcy.fitapet.domain.oauth.type.ProviderType; +import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; +import com.kcy.fitapet.global.common.security.oauth.OauthApplicationConfig; +import com.kcy.fitapet.global.common.security.oauth.kakao.KakaoApplicationConfig; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class OauthApplicationConfigHelper { + private final KakaoApplicationConfig kakaoApplicationConfig; + + public OauthApplicationConfig getOauthApplicationConfig(ProviderType provider) { + return switch (provider) { + case KAKAO -> kakaoApplicationConfig; + case GOOGLE -> null; + case APPLE -> null; + case NAVER -> null; + default -> throw new GlobalErrorException(OauthException.INVALID_PROVIDER); + }; + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthClientHelper.java b/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthClientHelper.java new file mode 100644 index 00000000..7871a0ef --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthClientHelper.java @@ -0,0 +1,24 @@ +package com.kcy.fitapet.domain.oauth.service.module; + +import com.kcy.fitapet.domain.oauth.exception.OauthException; +import com.kcy.fitapet.domain.oauth.type.ProviderType; +import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; +import com.kcy.fitapet.global.common.security.oauth.OauthClient; +import com.kcy.fitapet.global.common.security.oauth.kakao.KakaoOauthClient; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class OauthClientHelper { + private final KakaoOauthClient kakaoOauthClient; + + public OauthClient getOauthClient(ProviderType provider) { + return switch (provider) { + case KAKAO -> kakaoOauthClient; + case GOOGLE -> null; + case APPLE -> null; + default -> throw new GlobalErrorException(OauthException.INVALID_PROVIDER); + }; + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java b/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java new file mode 100644 index 00000000..82ac86fd --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java @@ -0,0 +1,34 @@ +package com.kcy.fitapet.domain.oauth.service.module; + +import com.kcy.fitapet.domain.member.domain.Member; +import com.kcy.fitapet.domain.oauth.dao.OauthRepository; +import com.kcy.fitapet.domain.oauth.domain.OauthAccount; +import com.kcy.fitapet.domain.oauth.exception.OauthException; +import com.kcy.fitapet.domain.oauth.type.ProviderType; +import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class OauthSearchService { + private final OauthRepository oauthRepository; + + @Transactional(readOnly = true) + public boolean isExistMember(Long oauthId, ProviderType provider) { + return oauthRepository.existsByOauthIdAndProvider(oauthId, provider); + } + + @Transactional(readOnly = true) + public boolean isExistEmail(String email) { + return oauthRepository.existsByEmail(email); + } + + @Transactional(readOnly = true) + public Member findMemberByOauthIdAndProvider(Long oauthId, ProviderType provider) { + OauthAccount oauthAccount = oauthRepository.findByOauthIdAndProvider(oauthId, provider) + .orElseThrow(() -> new GlobalErrorException(OauthException.NOT_FOUND_MEMBER)); + return oauthAccount.getMember(); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenService.java b/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenService.java index 7a764609..a2c04fda 100644 --- a/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenService.java +++ b/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenService.java @@ -1,6 +1,6 @@ package com.kcy.fitapet.global.common.redis.refresh; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorException; public interface RefreshTokenService { /** diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenServiceImpl.java b/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenServiceImpl.java index 1db59951..6f5276ab 100644 --- a/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenServiceImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/redis/refresh/RefreshTokenServiceImpl.java @@ -1,9 +1,9 @@ package com.kcy.fitapet.global.common.redis.refresh; -import com.kcy.fitapet.global.common.util.jwt.JwtUtil; -import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.security.jwt.JwtUtil; +import com.kcy.fitapet.global.common.security.jwt.dto.JwtUserInfo; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorCode; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/kcy/fitapet/global/common/resolver/access/AccessTokenInfoResolver.java b/src/main/java/com/kcy/fitapet/global/common/resolver/access/AccessTokenInfoResolver.java index 258cf45a..4e60f686 100644 --- a/src/main/java/com/kcy/fitapet/global/common/resolver/access/AccessTokenInfoResolver.java +++ b/src/main/java/com/kcy/fitapet/global/common/resolver/access/AccessTokenInfoResolver.java @@ -1,9 +1,9 @@ package com.kcy.fitapet.global.common.resolver.access; -import com.kcy.fitapet.global.common.util.jwt.AuthConstants; -import com.kcy.fitapet.global.common.util.jwt.JwtUtil; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.security.jwt.AuthConstants; +import com.kcy.fitapet.global.common.security.jwt.JwtUtil; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorCode; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.AccessLevel; @@ -12,7 +12,6 @@ import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; -import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; diff --git a/src/main/java/com/kcy/fitapet/global/common/response/handler/GlobalExceptionHandler.java b/src/main/java/com/kcy/fitapet/global/common/response/handler/GlobalExceptionHandler.java index b46fbc90..4e634296 100644 --- a/src/main/java/com/kcy/fitapet/global/common/response/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/kcy/fitapet/global/common/response/handler/GlobalExceptionHandler.java @@ -4,10 +4,9 @@ import com.kcy.fitapet.global.common.response.FailureResponse; import com.kcy.fitapet.global.common.response.code.ErrorCode; import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.hibernate.query.sqm.sql.ConversionException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; diff --git a/src/main/java/com/kcy/fitapet/global/common/security/authentication/CustomUserDetails.java b/src/main/java/com/kcy/fitapet/global/common/security/authentication/CustomUserDetails.java index a3c053ba..5ae503b1 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/authentication/CustomUserDetails.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/authentication/CustomUserDetails.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.kcy.fitapet.domain.member.domain.Member; import com.kcy.fitapet.domain.member.type.RoleType; -import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; +import com.kcy.fitapet.global.common.security.jwt.dto.JwtUserInfo; import lombok.Builder; import lombok.Getter; import org.springframework.security.core.GrantedAuthority; diff --git a/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java index 62ab2eb4..c889dff5 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java @@ -2,10 +2,10 @@ import com.kcy.fitapet.global.common.security.authentication.UserDetailServiceImpl; import com.kcy.fitapet.global.common.util.cookie.CookieUtil; -import com.kcy.fitapet.global.common.util.jwt.JwtUtil; -import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.security.jwt.JwtUtil; +import com.kcy.fitapet.global.common.security.jwt.dto.JwtUserInfo; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorCode; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorException; import com.kcy.fitapet.global.common.redis.forbidden.ForbiddenTokenService; import com.kcy.fitapet.global.common.redis.refresh.RefreshToken; import com.kcy.fitapet.global.common.redis.refresh.RefreshTokenService; @@ -30,7 +30,7 @@ import java.util.List; import java.util.regex.Pattern; -import static com.kcy.fitapet.global.common.util.jwt.AuthConstants.*; +import static com.kcy.fitapet.global.common.security.jwt.AuthConstants.*; /** * 지정한 URL 별로 JWT 유효성 검증을 수행하며, 직접적인 사용자 인증을 확인합니다. diff --git a/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtExceptionFilter.java b/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtExceptionFilter.java index 564f76fb..bf6f076b 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtExceptionFilter.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtExceptionFilter.java @@ -1,9 +1,9 @@ package com.kcy.fitapet.global.common.security.filter; import com.fasterxml.jackson.databind.ObjectMapper; -import com.kcy.fitapet.global.common.util.jwt.exception.JwtErrorCodeUtil; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorResponse; +import com.kcy.fitapet.global.common.security.jwt.exception.JwtErrorCodeUtil; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorResponse; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/AuthConstants.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/AuthConstants.java similarity index 89% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/AuthConstants.java rename to src/main/java/com/kcy/fitapet/global/common/security/jwt/AuthConstants.java index d06f20e0..8d7d3ae5 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/AuthConstants.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/AuthConstants.java @@ -1,4 +1,4 @@ -package com.kcy.fitapet.global.common.util.jwt; +package com.kcy.fitapet.global.common.security.jwt; import lombok.Getter; diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/JwtUtil.java similarity index 90% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java rename to src/main/java/com/kcy/fitapet/global/common/security/jwt/JwtUtil.java index 89a1249a..dc6d1075 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/JwtUtil.java @@ -1,8 +1,8 @@ -package com.kcy.fitapet.global.common.util.jwt; +package com.kcy.fitapet.global.common.security.jwt; -import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; -import com.kcy.fitapet.global.common.util.jwt.dto.SmsAuthInfo; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.security.jwt.dto.JwtUserInfo; +import com.kcy.fitapet.global.common.security.jwt.dto.SmsAuthInfo; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorException; import java.time.LocalDateTime; diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/JwtUtilImpl.java similarity index 93% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java rename to src/main/java/com/kcy/fitapet/global/common/security/jwt/JwtUtilImpl.java index 4f6c482b..bcb88990 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/JwtUtilImpl.java @@ -1,12 +1,12 @@ -package com.kcy.fitapet.global.common.util.jwt; +package com.kcy.fitapet.global.common.security.jwt; import com.kcy.fitapet.domain.member.type.RoleType; import com.kcy.fitapet.global.common.util.DateUtil; -import com.kcy.fitapet.global.common.util.jwt.exception.JwtErrorCodeUtil; -import com.kcy.fitapet.global.common.util.jwt.dto.JwtUserInfo; -import com.kcy.fitapet.global.common.util.jwt.dto.SmsAuthInfo; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.security.jwt.exception.JwtErrorCodeUtil; +import com.kcy.fitapet.global.common.security.jwt.dto.JwtUserInfo; +import com.kcy.fitapet.global.common.security.jwt.dto.SmsAuthInfo; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorCode; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; diff --git a/src/main/java/com/kcy/fitapet/global/common/security/jwt/dto/Jwt.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/dto/Jwt.java new file mode 100644 index 00000000..a5b228df --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/dto/Jwt.java @@ -0,0 +1,10 @@ +package com.kcy.fitapet.global.common.security.jwt.dto; + +import lombok.Builder; + +@Builder +public record Jwt( + String accessToken, + String refreshToken +) { +} diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/JwtUserInfo.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/dto/JwtUserInfo.java similarity index 79% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/JwtUserInfo.java rename to src/main/java/com/kcy/fitapet/global/common/security/jwt/dto/JwtUserInfo.java index c483a8fd..b5ba2a13 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/JwtUserInfo.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/dto/JwtUserInfo.java @@ -1,8 +1,8 @@ -package com.kcy.fitapet.global.common.util.jwt.dto; +package com.kcy.fitapet.global.common.security.jwt.dto; import com.kcy.fitapet.domain.member.domain.Member; import com.kcy.fitapet.domain.member.type.RoleType; -import com.kcy.fitapet.global.common.util.jwt.entity.JwtDto; +import com.kcy.fitapet.global.common.security.jwt.entity.JwtDto; import lombok.Builder; @Builder diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/SmsAuthInfo.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/dto/SmsAuthInfo.java similarity index 68% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/SmsAuthInfo.java rename to src/main/java/com/kcy/fitapet/global/common/security/jwt/dto/SmsAuthInfo.java index ce302697..b7fb9dd4 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/SmsAuthInfo.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/dto/SmsAuthInfo.java @@ -1,6 +1,6 @@ -package com.kcy.fitapet.global.common.util.jwt.dto; +package com.kcy.fitapet.global.common.security.jwt.dto; -import com.kcy.fitapet.global.common.util.jwt.entity.JwtDto; +import com.kcy.fitapet.global.common.security.jwt.entity.JwtDto; import lombok.Builder; @Builder diff --git a/src/main/java/com/kcy/fitapet/global/common/security/jwt/entity/JwtDto.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/entity/JwtDto.java new file mode 100644 index 00000000..94c610d7 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/entity/JwtDto.java @@ -0,0 +1,4 @@ +package com.kcy.fitapet.global.common.security.jwt.entity; + +public interface JwtDto { +} diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/AuthErrorCode.java similarity index 97% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java rename to src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/AuthErrorCode.java index 4142d232..52b5fe0d 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/AuthErrorCode.java @@ -1,4 +1,4 @@ -package com.kcy.fitapet.global.common.util.jwt.exception; +package com.kcy.fitapet.global.common.security.jwt.exception; import com.kcy.fitapet.global.common.response.code.StatusCode; import lombok.AccessLevel; diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorException.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/AuthErrorException.java similarity index 92% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorException.java rename to src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/AuthErrorException.java index a4716d1e..b03b8660 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorException.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/AuthErrorException.java @@ -1,4 +1,4 @@ -package com.kcy.fitapet.global.common.util.jwt.exception; +package com.kcy.fitapet.global.common.security.jwt.exception; import io.jsonwebtoken.JwtException; import lombok.Getter; diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorResponse.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/AuthErrorResponse.java similarity index 75% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorResponse.java rename to src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/AuthErrorResponse.java index 138d69a0..9f5edfb6 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorResponse.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/AuthErrorResponse.java @@ -1,4 +1,4 @@ -package com.kcy.fitapet.global.common.util.jwt.exception; +package com.kcy.fitapet.global.common.security.jwt.exception; public record AuthErrorResponse(String code, String message) { @Override public String toString() { diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/JwtErrorCodeUtil.java b/src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/JwtErrorCodeUtil.java similarity index 93% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/JwtErrorCodeUtil.java rename to src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/JwtErrorCodeUtil.java index 5c99fedd..bfb920f2 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/JwtErrorCodeUtil.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/jwt/exception/JwtErrorCodeUtil.java @@ -1,7 +1,5 @@ -package com.kcy.fitapet.global.common.util.jwt.exception; +package com.kcy.fitapet.global.common.security.jwt.exception; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthApplicationConfig.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthApplicationConfig.java new file mode 100644 index 00000000..8b7613b2 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthApplicationConfig.java @@ -0,0 +1,11 @@ +package com.kcy.fitapet.global.common.security.oauth; + +import java.util.List; + +public interface OauthApplicationConfig { + String getAuthorizationUri(); + String getClientId(); + String getClientSecret(); + String getClientName(); + List getScopes(); +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthClient.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthClient.java new file mode 100644 index 00000000..74870334 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthClient.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.global.common.security.oauth; + +import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKeyResponse; + +public interface OauthClient { + OIDCPublicKeyResponse getOIDCPublicKey(); +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCHelper.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCHelper.java index 3e0225b5..b4802644 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCHelper.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCHelper.java @@ -4,15 +4,25 @@ import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKey; import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKeyResponse; import lombok.RequiredArgsConstructor; -import lombok.experimental.Helper; +import org.springframework.stereotype.Component; -@Helper +@Component @RequiredArgsConstructor public class OauthOIDCHelper { private final OauthOIDCProvider oauthOIDCProvider; - public OIDCDecodePayload getPayloadFromIdToken(String token, String iss, String aud, OIDCPublicKeyResponse response) { - String kid = getKidFromUnsignedIdToken(token, iss, aud); + /** + * ID Token의 payload를 추출하는 메서드
+ * OAuth 2.0 spec에 따라 ID Token의 유효성 검사 수행
+ * @param token : idToken + * @param iss : ID Token을 발급한 provider의 URL + * @param aud : ID Token이 발급된 앱의 앱 키 + * @param nonce : 인증 서버 로그인 요청 시 전달한 임의의 문자열 + * @param response : 공개키 목록 + * @return OIDCDecodePayload : ID Token의 payload + */ + public OIDCDecodePayload getPayloadFromIdToken(String token, String iss, String aud, String nonce, OIDCPublicKeyResponse response) { + String kid = getKidFromUnsignedIdToken(token, iss, aud, nonce); OIDCPublicKey key = response.getKeys().stream() .filter(k -> k.kid().equals(kid)) @@ -22,7 +32,7 @@ public OIDCDecodePayload getPayloadFromIdToken(String token, String iss, String return oauthOIDCProvider.getOIDCTokenBody(token, key.n(), key.e()); } - private String getKidFromUnsignedIdToken(String token, String iss, String aud) { - return oauthOIDCProvider.getKidFromUnsignedTokenHeader(token, iss, aud); + private String getKidFromUnsignedIdToken(String token, String iss, String aud, String nonce) { + return oauthOIDCProvider.getKidFromUnsignedTokenHeader(token, iss, aud, nonce); } } diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProvider.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProvider.java index be35d966..4a9fa0d1 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProvider.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProvider.java @@ -5,8 +5,22 @@ @Component public interface OauthOIDCProvider { + /** + * ID Token의 header에서 kid를 추출하는 메서드 + * @param token : idToken + * @param iss : ID Token을 발급한 OAuth 2.0 제공자의 URL + * @param aud : ID Token이 발급된 앱의 앱 키 + * @param nonce : 인증 서버 로그인 요청 시 전달한 임의의 문자열 + * @return kid : ID Token의 서명에 사용된 공개키의 ID + */ + String getKidFromUnsignedTokenHeader(String token, String iss, String aud, String nonce); - String getKidFromUnsignedTokenHeader(String token, String iss, String aud); - + /** + * ID Token의 payload를 추출하는 메서드 + * @param token : idToken + * @param modulus : 공개키 모듈(n) + * @param exponent : 공개키 지수(e) + * @return OIDCDecodePayload : ID Token의 payload + */ OIDCDecodePayload getOIDCTokenBody(String token, String modulus, String exponent); } diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/OauthOIDCProviderImpl.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProviderImpl.java similarity index 78% rename from src/main/java/com/kcy/fitapet/global/common/util/jwt/OauthOIDCProviderImpl.java rename to src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProviderImpl.java index 4d63df16..b6f4574d 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/OauthOIDCProviderImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/OauthOIDCProviderImpl.java @@ -1,10 +1,9 @@ -package com.kcy.fitapet.global.common.util.jwt; +package com.kcy.fitapet.global.common.security.oauth; import com.kcy.fitapet.global.common.security.oauth.dto.OIDCDecodePayload; -import com.kcy.fitapet.global.common.security.oauth.OauthOIDCProvider; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; -import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; -import com.kcy.fitapet.global.common.util.jwt.exception.JwtErrorCodeUtil; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorCode; +import com.kcy.fitapet.global.common.security.jwt.exception.AuthErrorException; +import com.kcy.fitapet.global.common.security.jwt.exception.JwtErrorCodeUtil; import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; @@ -22,8 +21,8 @@ public class OauthOIDCProviderImpl implements OauthOIDCProvider { private final String RSA = "RSA"; @Override - public String getKidFromUnsignedTokenHeader(String token, String iss, String aud) { - return (String) getUnsignedTokenClaims(token, iss, aud).getHeader().get(KID); + public String getKidFromUnsignedTokenHeader(String token, String iss, String aud, String nonce) { + return (String) getUnsignedTokenClaims(token, iss, aud, nonce).getHeader().get(KID); } @Override @@ -37,11 +36,16 @@ public OIDCDecodePayload getOIDCTokenBody(String token, String modulus, String e body.get("email", String.class)); } - private Jwt getUnsignedTokenClaims(String token, String iss, String aud) { + /** + * ID Token의 header와 body를 Base64 방식으로 디코딩하는 메서드
+ * payload의 iss, aud, exp, nonce를 검증하고, 실패시 예외 처리 + */ + private Jwt getUnsignedTokenClaims(String token, String iss, String aud, String nonce) { try { return Jwts.parserBuilder() .requireAudience(aud) .requireIssuer(iss) + .require("nonce", nonce) .build() .parseClaimsJwt(getUnsignedToken(token)); } catch (JwtException e) { @@ -52,12 +56,18 @@ private Jwt getUnsignedTokenClaims(String token, String iss, Str } } + /** + * Token의 signature를 제거하는 메서드 + */ private String getUnsignedToken(String token){ String[] splitToken = token.split("\\."); if (splitToken.length != 3) throw new AuthErrorException(AuthErrorCode.INVALID_TOKEN, "Invalid token"); return splitToken[0] + "." + splitToken[1] + "."; } + /** + * 공개키로 서명을 검증하는 메서드 + */ private Jws getOIDCTokenJws(String token, String modulus, String exponent) { try { return Jwts.parserBuilder() @@ -75,6 +85,9 @@ private Jws getOIDCTokenJws(String token, String modulus, String exponen } } + /** + * n, e 조합으로 공개키를 생성하는 메서드 + */ private Key getRSAPublicKey(String modulus, String exponent) throws NoSuchAlgorithmException, InvalidKeySpecException { KeyFactory keyFactory = KeyFactory.getInstance(RSA); byte[] decodeN = Base64.getUrlDecoder().decode(modulus); diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoApplicationConfig.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoApplicationConfig.java new file mode 100644 index 00000000..4316b89b --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoApplicationConfig.java @@ -0,0 +1,28 @@ +package com.kcy.fitapet.global.common.security.oauth.kakao; + +import com.kcy.fitapet.global.common.security.oauth.OauthApplicationConfig; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +@Getter +@Validated +@Component +@ConfigurationProperties(prefix = "kakao") +@ConfigurationPropertiesBinding +public class KakaoApplicationConfig implements OauthApplicationConfig { + @NotEmpty + private String authorizationUri; + @NotEmpty + private String clientId; + @NotEmpty + private String clientSecret; + @NotEmpty + private String clientName; + private List scopes; +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoOauthClient.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoOauthClient.java index e6e540b4..97740f26 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoOauthClient.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KakaoOauthClient.java @@ -1,5 +1,6 @@ package com.kcy.fitapet.global.common.security.oauth.kakao; +import com.kcy.fitapet.global.common.security.oauth.OauthClient; import com.kcy.fitapet.global.common.security.oauth.dto.OIDCPublicKeyResponse; import com.kcy.fitapet.global.config.feign.KakaoOauthConfig; import org.springframework.cache.annotation.Cacheable; @@ -11,8 +12,9 @@ url = "${security.oauth2.client.provider.authorization-uri}", configuration = KakaoOauthConfig.class ) -public interface KakaoOauthClient { +public interface KakaoOauthClient extends OauthClient { + @Override @Cacheable(value = "KakaoOauth", cacheManager = "oidcCacheManger") @GetMapping("/.well-knowm/jwks.json") - OIDCPublicKeyResponse getKakaoOIDCPublicKey(); + OIDCPublicKeyResponse getOIDCPublicKey(); } diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KauthErrorDecoder.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KauthErrorDecoder.java deleted file mode 100644 index b37165db..00000000 --- a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/KauthErrorDecoder.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.kcy.fitapet.global.common.security.oauth.kakao; - -import feign.Response; -import feign.codec.ErrorDecoder; - -public class KauthErrorDecoder implements ErrorDecoder { - @Override - public Exception decode(String methodKey, Response response) { - return null; - } -} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/dto/KakaoKauthErrorResponse.java b/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/dto/KakaoKauthErrorResponse.java deleted file mode 100644 index c143d301..00000000 --- a/src/main/java/com/kcy/fitapet/global/common/security/oauth/kakao/dto/KakaoKauthErrorResponse.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.kcy.fitapet.global.common.security.oauth.kakao.dto; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import com.kcy.fitapet.global.common.response.code.ErrorCode; -import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; -import feign.Response; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.io.IOException; -import java.io.InputStream; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public class KakaoKauthErrorResponse { - private String error; - private String errorCode; - private String errorDescription; - - public static KakaoKauthErrorResponse from(Response response) { - try (InputStream bodyIs = response.body().asInputStream()) { - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.readValue(bodyIs, KakaoKauthErrorResponse.class); - } catch (IOException e) { - throw new GlobalErrorException(ErrorCode.INTERNAL_SERVER_ERROR); - } - } -} diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/Jwt.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/Jwt.java deleted file mode 100644 index 7395e03a..00000000 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/dto/Jwt.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.kcy.fitapet.global.common.util.jwt.dto; - -public record Jwt( - String accessToken, - String refreshToken -) { - public static Jwt of(String accessToken, String refreshToken) { - return new Jwt(accessToken, refreshToken); - } -} diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtDto.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtDto.java deleted file mode 100644 index 27f54a5d..00000000 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtDto.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.kcy.fitapet.global.common.util.jwt.entity; - -public interface JwtDto { -} diff --git a/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java b/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java index f2280e15..71f04c43 100644 --- a/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java @@ -4,6 +4,6 @@ import org.springframework.context.annotation.Configuration; @Configuration -@EnableFeignClients +@EnableFeignClients(basePackages = "com.kcy.fitapet") public class FeignConfig { } diff --git a/src/main/java/com/kcy/fitapet/global/config/feign/KakaoOauthConfig.java b/src/main/java/com/kcy/fitapet/global/config/feign/KakaoOauthConfig.java index a55cb9c9..eaf73b20 100644 --- a/src/main/java/com/kcy/fitapet/global/config/feign/KakaoOauthConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/feign/KakaoOauthConfig.java @@ -1,19 +1,14 @@ package com.kcy.fitapet.global.config.feign; -import com.kcy.fitapet.global.common.security.oauth.kakao.KauthErrorDecoder; import feign.codec.Encoder; -import feign.codec.ErrorDecoder; import feign.form.FormEncoder; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import feign.okhttp.OkHttpClient; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -@Import(KauthErrorDecoder.class) public class KakaoOauthConfig { @Bean - @ConditionalOnMissingBean(value = ErrorDecoder.class) - public KauthErrorDecoder commonFeignErrorDecoder() { - return new KauthErrorDecoder(); + public OkHttpClient client() { + return new OkHttpClient(); } @Bean diff --git a/src/main/java/com/kcy/fitapet/global/config/security/SecurityFilterConfig.java b/src/main/java/com/kcy/fitapet/global/config/security/SecurityFilterConfig.java index be2ca950..8e8b113b 100644 --- a/src/main/java/com/kcy/fitapet/global/config/security/SecurityFilterConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/security/SecurityFilterConfig.java @@ -5,7 +5,7 @@ import com.kcy.fitapet.global.common.security.filter.JwtAuthenticationFilter; import com.kcy.fitapet.global.common.security.filter.JwtExceptionFilter; import com.kcy.fitapet.global.common.util.cookie.CookieUtil; -import com.kcy.fitapet.global.common.util.jwt.JwtUtil; +import com.kcy.fitapet.global.common.security.jwt.JwtUtil; import com.kcy.fitapet.global.common.redis.forbidden.ForbiddenTokenService; import com.kcy.fitapet.global.common.redis.refresh.RefreshTokenService; import lombok.AccessLevel;