diff --git a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/auth/helper/OauthOidcHelper.java b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/auth/helper/OauthOidcHelper.java
new file mode 100644
index 000000000..9ed00f006
--- /dev/null
+++ b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/auth/helper/OauthOidcHelper.java
@@ -0,0 +1,41 @@
+package kr.co.pennyway.api.apis.auth.helper;
+
+import kr.co.pennyway.common.annotation.Helper;
+import kr.co.pennyway.infra.common.oidc.OauthOidcProvider;
+import kr.co.pennyway.infra.common.oidc.OidcDecodePayload;
+import kr.co.pennyway.infra.common.oidc.OidcPublicKey;
+import kr.co.pennyway.infra.common.oidc.OidcPublicKeyResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Helper
+@RequiredArgsConstructor
+@Slf4j
+public class OauthOidcHelper {
+ private final OauthOidcProvider oauthOIDCProvider;
+
+ /**
+ * 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))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("No matching key found"));
+ return oauthOIDCProvider.getOIDCTokenBody(token, key.n(), key.e());
+ }
+
+ private String getKidFromUnsignedIdToken(String token, String iss, String aud, String nonce) {
+ return oauthOIDCProvider.getKidFromUnsignedTokenHeader(token, iss, aud, nonce);
+ }
+}
diff --git a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/config/InfraConfig.java b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/config/InfraConfig.java
new file mode 100644
index 000000000..e62ecea67
--- /dev/null
+++ b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/config/InfraConfig.java
@@ -0,0 +1,18 @@
+package kr.co.pennyway.api.config;
+
+import kr.co.pennyway.infra.common.properties.AppleOidcProperties;
+import kr.co.pennyway.infra.common.properties.GoogleOidcProperties;
+import kr.co.pennyway.infra.common.properties.KakaoOidcProperties;
+import kr.co.pennyway.infra.common.properties.ServerProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableConfigurationProperties({
+ ServerProperties.class,
+ AppleOidcProperties.class,
+ GoogleOidcProperties.class,
+ KakaoOidcProperties.class
+})
+public class InfraConfig {
+}
diff --git a/pennyway-common/src/main/java/kr/co/pennyway/common/util/MapUtils.java b/pennyway-common/src/main/java/kr/co/pennyway/common/util/MapUtils.java
new file mode 100644
index 000000000..773e6dd2a
--- /dev/null
+++ b/pennyway-common/src/main/java/kr/co/pennyway/common/util/MapUtils.java
@@ -0,0 +1,20 @@
+package kr.co.pennyway.common.util;
+
+import java.util.Map;
+
+/**
+ * Map 관련 유틸리티 클래스
+ *
+ * @author YANG JAESEO
+ */
+public class MapUtils {
+ /**
+ * key에 해당하는 값을 반환하고, key가 없을 경우 defaultValue를 반환한다.
+ */
+ public static V getObject(Map map, K key, V defaultValue) {
+ if (map == null || key == null) {
+ return defaultValue;
+ }
+ return map.getOrDefault(key, defaultValue);
+ }
+}
\ No newline at end of file
diff --git a/pennyway-infra/build.gradle b/pennyway-infra/build.gradle
index f2e1c0b01..8631172c7 100644
--- a/pennyway-infra/build.gradle
+++ b/pennyway-infra/build.gradle
@@ -1,5 +1,5 @@
-bootJar {enabled = false}
-jar {enabled = true}
+bootJar { enabled = false }
+jar { enabled = true }
dependencies {
implementation project(':pennyway-common')
@@ -11,4 +11,9 @@ dependencies {
/* redis */
api 'org.springframework.boot:spring-boot-starter-data-redis'
+
+ /* feign */
+ implementation platform("org.springframework.cloud:spring-cloud-dependencies:2023.0.1")
+ implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.1'
+ implementation 'io.github.openfeign:feign-okhttp:13.2'
}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/PennywayInfraApplication.java b/pennyway-infra/src/main/java/kr/co/pennyway/PennywayInfraApplication.java
deleted file mode 100644
index 0bc66b4bb..000000000
--- a/pennyway-infra/src/main/java/kr/co/pennyway/PennywayInfraApplication.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package kr.co.pennyway;
-
-public class PennywayInfraApplication {
-
-}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/PennywayInfraApplication.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/PennywayInfraApplication.java
new file mode 100644
index 000000000..b4f57535d
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/PennywayInfraApplication.java
@@ -0,0 +1,8 @@
+package kr.co.pennyway.infra;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class PennywayInfraApplication {
+
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/client/apple/oidc/AppleOidcClient.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/client/apple/oidc/AppleOidcClient.java
new file mode 100644
index 000000000..6c7078132
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/client/apple/oidc/AppleOidcClient.java
@@ -0,0 +1,22 @@
+package kr.co.pennyway.infra.client.apple.oidc;
+
+import kr.co.pennyway.infra.common.oidc.OauthOidcClient;
+import kr.co.pennyway.infra.common.oidc.OidcPublicKeyResponse;
+import kr.co.pennyway.infra.config.DefaultFeignConfig;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@FeignClient(
+ name = "AppleOauthClient",
+ url = "${oauth2.client.provider.apple.jwks-uri}",
+ configuration = DefaultFeignConfig.class,
+ qualifiers = "appleOauthClient",
+ primary = false
+)
+public interface AppleOidcClient extends OauthOidcClient {
+ @Override
+ @Cacheable(value = "AppleOauth", cacheManager = "oidcCacheManager")
+ @GetMapping("/auth/keys")
+ OidcPublicKeyResponse getOIDCPublicKey();
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/client/google/oidc/GoogleOidcClient.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/client/google/oidc/GoogleOidcClient.java
new file mode 100644
index 000000000..297fdea98
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/client/google/oidc/GoogleOidcClient.java
@@ -0,0 +1,22 @@
+package kr.co.pennyway.infra.client.google.oidc;
+
+import kr.co.pennyway.infra.common.oidc.OauthOidcClient;
+import kr.co.pennyway.infra.common.oidc.OidcPublicKeyResponse;
+import kr.co.pennyway.infra.config.DefaultFeignConfig;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@FeignClient(
+ name = "GoogleOauthClient",
+ url = "${oauth2.client.provider.google.jwks-uri}",
+ configuration = DefaultFeignConfig.class,
+ qualifiers = "googleOauthClient",
+ primary = false
+)
+public interface GoogleOidcClient extends OauthOidcClient {
+ @Override
+ @Cacheable(value = "GoogleOauth", cacheManager = "oidcCacheManager")
+ @GetMapping("/oauth2/v3/certs")
+ OidcPublicKeyResponse getOIDCPublicKey();
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/client/kakao/oidc/KakaoOidcClient.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/client/kakao/oidc/KakaoOidcClient.java
new file mode 100644
index 000000000..b9603d3bd
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/client/kakao/oidc/KakaoOidcClient.java
@@ -0,0 +1,21 @@
+package kr.co.pennyway.infra.client.kakao.oidc;
+
+import kr.co.pennyway.infra.common.oidc.OauthOidcClient;
+import kr.co.pennyway.infra.common.oidc.OidcPublicKeyResponse;
+import kr.co.pennyway.infra.config.DefaultFeignConfig;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@FeignClient(
+ name = "KakaoOauthClient",
+ url = "${oauth2.client.provider.kakao.jwks-uri}",
+ configuration = DefaultFeignConfig.class,
+ qualifiers = "kakaoOauthClient"
+)
+public interface KakaoOidcClient extends OauthOidcClient {
+ @Override
+ @Cacheable(value = "KakaoOauth", cacheManager = "oidcCacheManager")
+ @GetMapping("/.well-known/jwks.json")
+ OidcPublicKeyResponse getOIDCPublicKey();
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/EnablePennywayInfraConfig.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/EnablePennywayInfraConfig.java
new file mode 100644
index 000000000..ce58cee3f
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/EnablePennywayInfraConfig.java
@@ -0,0 +1,15 @@
+package kr.co.pennyway.infra.common.importer;
+
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Import(PennywayInfraConfigImportSelector.class)
+public @interface EnablePennywayInfraConfig {
+ PennywayInfraConfigGroup[] value();
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/PennywayInfraConfig.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/PennywayInfraConfig.java
new file mode 100644
index 000000000..8a7398e5a
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/PennywayInfraConfig.java
@@ -0,0 +1,7 @@
+package kr.co.pennyway.infra.common.importer;
+
+/**
+ * Pennyway Infra의 Configurations를 나타내는 Marker Interface
+ */
+public interface PennywayInfraConfig {
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/PennywayInfraConfigGroup.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/PennywayInfraConfigGroup.java
new file mode 100644
index 000000000..44cd79d98
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/PennywayInfraConfigGroup.java
@@ -0,0 +1,12 @@
+package kr.co.pennyway.infra.common.importer;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum PennywayInfraConfigGroup {
+ ;
+
+ private final Class extends PennywayInfraConfig> configClass;
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/PennywayInfraConfigImportSelector.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/PennywayInfraConfigImportSelector.java
new file mode 100644
index 000000000..05e9cc0cb
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/importer/PennywayInfraConfigImportSelector.java
@@ -0,0 +1,25 @@
+package kr.co.pennyway.infra.common.importer;
+
+import kr.co.pennyway.common.util.MapUtils;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.context.annotation.DeferredImportSelector;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.lang.NonNull;
+
+import java.util.Arrays;
+import java.util.Map;
+
+public class PennywayInfraConfigImportSelector implements DeferredImportSelector {
+ @NotNull
+ @Override
+ public String[] selectImports(@NonNull AnnotationMetadata metadata) {
+ return Arrays.stream(getGroups(metadata))
+ .map(v -> v.getConfigClass().getName())
+ .toArray(String[]::new);
+ }
+
+ private PennywayInfraConfigGroup[] getGroups(AnnotationMetadata metadata) {
+ Map attributes = metadata.getAnnotationAttributes(EnablePennywayInfraConfig.class.getName());
+ return (PennywayInfraConfigGroup[]) MapUtils.getObject(attributes, "value", new PennywayInfraConfigGroup[]{});
+ }
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcClient.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcClient.java
new file mode 100644
index 000000000..0c4d217a1
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcClient.java
@@ -0,0 +1,5 @@
+package kr.co.pennyway.infra.common.oidc;
+
+public interface OauthOidcClient {
+ OidcPublicKeyResponse getOIDCPublicKey();
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcClientProperties.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcClientProperties.java
new file mode 100644
index 000000000..8b03875a6
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcClientProperties.java
@@ -0,0 +1,7 @@
+package kr.co.pennyway.infra.common.oidc;
+
+public interface OauthOidcClientProperties {
+ String getJwksUri();
+
+ String getSecret();
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcProvider.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcProvider.java
new file mode 100644
index 000000000..57bd3938f
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcProvider.java
@@ -0,0 +1,24 @@
+package kr.co.pennyway.infra.common.oidc;
+
+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);
+
+ /**
+ * 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/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcProviderImpl.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcProviderImpl.java
new file mode 100644
index 000000000..2010d14b2
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OauthOidcProviderImpl.java
@@ -0,0 +1,105 @@
+package kr.co.pennyway.infra.common.oidc;
+
+import io.jsonwebtoken.*;
+import kr.co.pennyway.infra.common.exception.JwtErrorCode;
+import kr.co.pennyway.infra.common.exception.JwtErrorException;
+import kr.co.pennyway.infra.common.util.JwtErrorCodeUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Base64;
+
+@Slf4j
+@Component
+public class OauthOidcProviderImpl implements OauthOidcProvider {
+ private static final String KID = "kid";
+ private static final String RSA = "RSA";
+
+ @Override
+ public String getKidFromUnsignedTokenHeader(String token, String iss, String aud, String nonce) {
+ return (String) getUnsignedTokenClaims(token, iss, aud, nonce).getHeader().get(KID);
+ }
+
+ @Override
+ public OidcDecodePayload getOIDCTokenBody(String token, String modulus, String exponent) {
+ Claims body = getOIDCTokenJws(token, modulus, exponent).getPayload();
+ String aud = body.getAudience().iterator().next(); // TODO: 이전 버전과 다르게 aud가 Set으로 변경되어 있음. 테스트 필요
+ log.debug("aud : {}", aud);
+
+ return new OidcDecodePayload(
+ body.getIssuer(),
+ aud,
+ body.getSubject(),
+ body.get("email", String.class));
+ }
+
+ /**
+ * ID Token의 header와 body를 Base64 방식으로 디코딩하는 메서드
+ * payload의 iss, aud, exp, nonce를 검증하고, 실패시 예외 처리
+ */
+ private Jwt getUnsignedTokenClaims(String token, String iss, String aud, String nonce) {
+ try {
+ return Jwts.parser()
+ .requireAudience(aud)
+ .requireIssuer(iss)
+// .require("nonce", nonce) // 현재는 nonce를 사용하지 않음
+ .build()
+ .parseUnsecuredClaims(getUnsignedToken(token)); // TODO: 기존 방식은 parseClaimsJwt(getUnsignedToken(token)); -> 변경한 코드 정상 동작 여부 확인 필요
+ } catch (JwtException e) {
+ final JwtErrorCode errorCode = JwtErrorCodeUtil.determineErrorCode(e, JwtErrorCode.FAILED_AUTHENTICATION);
+
+ log.warn("Error code : {}, Error - {}, {}", errorCode, e.getClass(), e.getMessage());
+ throw new JwtErrorException(errorCode);
+ }
+ }
+
+ /**
+ * Token의 signature를 제거하는 메서드
+ */
+ private String getUnsignedToken(String token) {
+ String[] splitToken = token.split("\\.");
+ if (splitToken.length != 3) throw new JwtErrorException(JwtErrorCode.MALFORMED_TOKEN);
+ return splitToken[0] + "." + splitToken[1] + ".";
+ }
+
+ /**
+ * 공개키로 서명을 검증하는 메서드
+ */
+ private Jws getOIDCTokenJws(String token, String modulus, String exponent) {
+ try {
+ log.info("token : {}", token);
+ return Jwts.parser()
+ .verifyWith(getRSAPublicKey(modulus, exponent))
+ .build()
+ .parseSignedClaims(token);
+ } catch (JwtException e) {
+ final JwtErrorCode errorCode = JwtErrorCodeUtil.determineErrorCode(e, JwtErrorCode.FAILED_AUTHENTICATION);
+
+ log.warn("Error code : {}, Error - {}, {}", errorCode, e.getClass(), e.getMessage());
+ throw new JwtErrorException(errorCode);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ log.warn("Error - {}, {}", e.getClass(), e.getMessage());
+ throw new JwtErrorException(JwtErrorCode.MALFORMED_TOKEN);
+ }
+ }
+
+ /**
+ * n, e 조합으로 공개키를 생성하는 메서드
+ */
+ private PublicKey getRSAPublicKey(String modulus, String exponent) throws NoSuchAlgorithmException, InvalidKeySpecException {
+ KeyFactory keyFactory = KeyFactory.getInstance(RSA);
+ byte[] decodeN = Base64.getUrlDecoder().decode(modulus);
+ byte[] decodeE = Base64.getUrlDecoder().decode(exponent);
+ BigInteger n = new BigInteger(1, decodeN);
+ BigInteger e = new BigInteger(1, decodeE);
+
+ RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
+ return keyFactory.generatePublic(publicKeySpec);
+ }
+}
\ No newline at end of file
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OidcDecodePayload.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OidcDecodePayload.java
new file mode 100644
index 000000000..7049056fb
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OidcDecodePayload.java
@@ -0,0 +1,12 @@
+package kr.co.pennyway.infra.common.oidc;
+
+public record OidcDecodePayload(
+ /* issuer */
+ String iss,
+ /* client id */
+ String aud,
+ /* aouth provider account unique id */
+ String sub,
+ String email
+) {
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OidcPublicKey.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OidcPublicKey.java
new file mode 100644
index 000000000..0fe4011f1
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OidcPublicKey.java
@@ -0,0 +1,11 @@
+package kr.co.pennyway.infra.common.oidc;
+
+public record OidcPublicKey(
+ String kid,
+ String kty,
+ String alg,
+ String use,
+ String n,
+ String e
+) {
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OidcPublicKeyResponse.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OidcPublicKeyResponse.java
new file mode 100644
index 000000000..15a392c67
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/oidc/OidcPublicKeyResponse.java
@@ -0,0 +1,19 @@
+package kr.co.pennyway.infra.common.oidc;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Getter
+@NoArgsConstructor
+public class OidcPublicKeyResponse {
+ List keys;
+
+ @Override
+ public String toString() {
+ return "OIDCPublicKeyResponse{" +
+ "keys=" + keys +
+ '}';
+ }
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/properties/AppleOidcProperties.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/properties/AppleOidcProperties.java
new file mode 100644
index 000000000..d953b2285
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/properties/AppleOidcProperties.java
@@ -0,0 +1,14 @@
+package kr.co.pennyway.infra.common.properties;
+
+import kr.co.pennyway.infra.common.oidc.OauthOidcClientProperties;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@RequiredArgsConstructor
+@ConfigurationProperties(prefix = "oauth2.client.provider.apple")
+public class AppleOidcProperties implements OauthOidcClientProperties {
+ private final String jwksUri;
+ private final String secret;
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/properties/GoogleOidcProperties.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/properties/GoogleOidcProperties.java
new file mode 100644
index 000000000..b61e61078
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/properties/GoogleOidcProperties.java
@@ -0,0 +1,14 @@
+package kr.co.pennyway.infra.common.properties;
+
+import kr.co.pennyway.infra.common.oidc.OauthOidcClientProperties;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@RequiredArgsConstructor
+@ConfigurationProperties(prefix = "oauth2.client.provider.google")
+public class GoogleOidcProperties implements OauthOidcClientProperties {
+ private final String jwksUri;
+ private final String secret;
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/properties/KakaoOidcProperties.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/properties/KakaoOidcProperties.java
new file mode 100644
index 000000000..e7d3f90a2
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/common/properties/KakaoOidcProperties.java
@@ -0,0 +1,14 @@
+package kr.co.pennyway.infra.common.properties;
+
+import kr.co.pennyway.infra.common.oidc.OauthOidcClientProperties;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@RequiredArgsConstructor
+@ConfigurationProperties(prefix = "oauth2.client.provider.kakao")
+public class KakaoOidcProperties implements OauthOidcClientProperties {
+ private final String jwksUri;
+ private final String secret;
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/CacheConfig.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/CacheConfig.java
index 4c51e7a59..a84c56992 100644
--- a/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/CacheConfig.java
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/CacheConfig.java
@@ -100,7 +100,7 @@ public CacheManager securityUserCacheManager(@InfraRedisConnectionFactory RedisC
@Bean
@OidcCacheManager
- public CacheManager oidcCacheManger(@InfraRedisConnectionFactory RedisConnectionFactory cf) {
+ public CacheManager oidcCacheManager(@InfraRedisConnectionFactory RedisConnectionFactory cf) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/ConfigurationPropertiesConfig.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/ConfigurationPropertiesConfig.java
deleted file mode 100644
index 63c859194..000000000
--- a/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/ConfigurationPropertiesConfig.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package kr.co.pennyway.infra.config;
-
-import kr.co.pennyway.infra.common.properties.ServerProperties;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Configuration;
-
-@EnableConfigurationProperties({
- ServerProperties.class
-})
-@Configuration
-public class ConfigurationPropertiesConfig {
-}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/DefaultFeignConfig.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/DefaultFeignConfig.java
new file mode 100644
index 000000000..07ee11df4
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/DefaultFeignConfig.java
@@ -0,0 +1,18 @@
+package kr.co.pennyway.infra.config;
+
+import feign.codec.Encoder;
+import feign.form.FormEncoder;
+import feign.okhttp.OkHttpClient;
+import org.springframework.context.annotation.Bean;
+
+public class DefaultFeignConfig {
+ @Bean
+ public OkHttpClient client() {
+ return new OkHttpClient();
+ }
+
+ @Bean
+ Encoder formEncoder() {
+ return new FormEncoder();
+ }
+}
diff --git a/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/FeignConfig.java b/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/FeignConfig.java
new file mode 100644
index 000000000..564fd5057
--- /dev/null
+++ b/pennyway-infra/src/main/java/kr/co/pennyway/infra/config/FeignConfig.java
@@ -0,0 +1,13 @@
+package kr.co.pennyway.infra.config;
+
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.cloud.openfeign.FeignAutoConfiguration;
+import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableFeignClients(basePackages = "kr.co.pennyway.infra")
+@ImportAutoConfiguration({FeignAutoConfiguration.class, HttpClient5FeignConfiguration.class})
+public class FeignConfig {
+}
diff --git a/pennyway-infra/src/main/resources/application-infra.yml b/pennyway-infra/src/main/resources/application-infra.yml
index f9c75be13..5b963497d 100644
--- a/pennyway-infra/src/main/resources/application-infra.yml
+++ b/pennyway-infra/src/main/resources/application-infra.yml
@@ -10,6 +10,19 @@ pennyway:
local: ${PENNYWAY_DOMAIN_LOCAL}
dev: ${PENNYWAY_DOMAIN_DEV}
+oauth2:
+ client:
+ provider:
+ kakao:
+ jwks-uri: ${KAKAO_JWKS_URI}
+ secret: ${KAKAO_CLIENT_SECRET}
+ google:
+ jwks-uri: ${GOOGLE_JWKS_URI}
+ secret: ${GOOGLE_CLIENT_SECRET}
+ apple:
+ jwks-uri: ${APPLE_JWKS_URI}
+ secret: ${APPLE_CLIENT_SECRET}
+
---
spring:
config: