From 4192b0351c0f082e076a88b131449f7aaca01274 Mon Sep 17 00:00:00 2001 From: seungheonlee Date: Wed, 10 Sep 2025 19:47:15 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20redis=20=EC=BA=90=EC=8B=B1=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OIDC JWK 및 SecurityUser에 대해 캐싱 적용 --- .../rabbitmqprac/config/RedisCacheConfig.java | 113 ++++++++++++++++++ .../auth/service/UserDetailServiceImpl.java | 2 + .../InfraRedisConnectionFactory.java | 17 +++ .../global/annotation/OidcCacheManager.java | 18 +++ .../annotation/SecurityUserCacheManager.java | 19 +++ .../global/consant/CacheManagerType.java | 6 + .../infra/oauth/client/GoogleOidcClient.java | 2 + .../infra/oauth/client/KakaoOidcClient.java | 2 + 8 files changed, 179 insertions(+) create mode 100644 src/main/java/com/rabbitmqprac/config/RedisCacheConfig.java create mode 100644 src/main/java/com/rabbitmqprac/global/annotation/InfraRedisConnectionFactory.java create mode 100644 src/main/java/com/rabbitmqprac/global/annotation/OidcCacheManager.java create mode 100644 src/main/java/com/rabbitmqprac/global/annotation/SecurityUserCacheManager.java create mode 100644 src/main/java/com/rabbitmqprac/global/consant/CacheManagerType.java diff --git a/src/main/java/com/rabbitmqprac/config/RedisCacheConfig.java b/src/main/java/com/rabbitmqprac/config/RedisCacheConfig.java new file mode 100644 index 0000000..052e9de --- /dev/null +++ b/src/main/java/com/rabbitmqprac/config/RedisCacheConfig.java @@ -0,0 +1,113 @@ +package com.rabbitmqprac.config; + +import com.rabbitmqprac.global.annotation.InfraRedisConnectionFactory; +import com.rabbitmqprac.global.annotation.OidcCacheManager; +import com.rabbitmqprac.global.annotation.SecurityUserCacheManager; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.cache.CacheKeyPrefix; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; + +@Configuration +@EnableCaching +public class RedisCacheConfig { + @Value("${spring.data.redis.host}") + private String host; + @Value("${spring.data.redis.port}") + private int port; + + private final long defaultCacheTtlSec = 60; + private final long securityUserCacheTtlSec = 30; + private final long oidcCacheTtlDay = 3; + + @Bean + @Primary + @InfraRedisConnectionFactory + public RedisConnectionFactory infraRedisConnectionFactory() { + RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port); + LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder().build(); + return new LettuceConnectionFactory(config, clientConfig); + } + + /** + * CacheManager를 명시하지 않을 경우 default로 사용되는 CacheManager + */ + @Bean + @Primary + public CacheManager defaultCacheManager(@InfraRedisConnectionFactory RedisConnectionFactory redisConnectionFactory) { + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .disableCachingNullValues() + .computePrefixWith(CacheKeyPrefix.simple()) + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()) + ) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()) + ) + .entryTtl(Duration.ofSeconds(defaultCacheTtlSec)); + + return RedisCacheManager.RedisCacheManagerBuilder + .fromConnectionFactory(redisConnectionFactory) + .cacheDefaults(config) + .build(); + } + + @Bean + @SecurityUserCacheManager + public CacheManager securityUserCacheManager(@InfraRedisConnectionFactory RedisConnectionFactory redisConnectionFactory) { + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .disableCachingNullValues() + .computePrefixWith(CacheKeyPrefix.simple()) + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new StringRedisSerializer() + ) + ) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new GenericJackson2JsonRedisSerializer() + ) + ) + .entryTtl(Duration.ofSeconds(securityUserCacheTtlSec)); + + return RedisCacheManager.RedisCacheManagerBuilder + .fromConnectionFactory(redisConnectionFactory) + .cacheDefaults(config) + .build(); + } + + @Bean + @OidcCacheManager + public CacheManager oidcCacheManager(@InfraRedisConnectionFactory RedisConnectionFactory cf) { + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new StringRedisSerializer() + )) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new GenericJackson2JsonRedisSerializer() + )) + .entryTtl(Duration.ofDays(oidcCacheTtlDay)); + + return RedisCacheManager + .RedisCacheManagerBuilder + .fromConnectionFactory(cf) + .cacheDefaults(config) + .build(); + } +} diff --git a/src/main/java/com/rabbitmqprac/domain/context/auth/service/UserDetailServiceImpl.java b/src/main/java/com/rabbitmqprac/domain/context/auth/service/UserDetailServiceImpl.java index a29b2f1..292afed 100644 --- a/src/main/java/com/rabbitmqprac/domain/context/auth/service/UserDetailServiceImpl.java +++ b/src/main/java/com/rabbitmqprac/domain/context/auth/service/UserDetailServiceImpl.java @@ -4,6 +4,7 @@ import com.rabbitmqprac.domain.persistence.user.entity.User; import com.rabbitmqprac.infra.security.authentication.SecurityUserDetails; import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Primary; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -17,6 +18,7 @@ public class UserDetailServiceImpl implements UserDetailsService { private final UserService userService; @Override + @Cacheable(value = "securityUser", key = "#userId", unless = "#result == null", cacheManager = "securityUserCacheManager") public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { User user = userService.readUser(Long.parseLong(userId)); return SecurityUserDetails.from(user); diff --git a/src/main/java/com/rabbitmqprac/global/annotation/InfraRedisConnectionFactory.java b/src/main/java/com/rabbitmqprac/global/annotation/InfraRedisConnectionFactory.java new file mode 100644 index 0000000..16e9608 --- /dev/null +++ b/src/main/java/com/rabbitmqprac/global/annotation/InfraRedisConnectionFactory.java @@ -0,0 +1,17 @@ +package com.rabbitmqprac.global.annotation; + +import org.springframework.beans.factory.annotation.Qualifier; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, + ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier("infraRedisConnectionFactory") +public @interface InfraRedisConnectionFactory { +} diff --git a/src/main/java/com/rabbitmqprac/global/annotation/OidcCacheManager.java b/src/main/java/com/rabbitmqprac/global/annotation/OidcCacheManager.java new file mode 100644 index 0000000..dba34ad --- /dev/null +++ b/src/main/java/com/rabbitmqprac/global/annotation/OidcCacheManager.java @@ -0,0 +1,18 @@ +package com.rabbitmqprac.global.annotation; + +import com.rabbitmqprac.global.consant.CacheManagerType; +import org.springframework.beans.factory.annotation.Qualifier; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, + ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier(CacheManagerType.OIDC) +public @interface OidcCacheManager { +} diff --git a/src/main/java/com/rabbitmqprac/global/annotation/SecurityUserCacheManager.java b/src/main/java/com/rabbitmqprac/global/annotation/SecurityUserCacheManager.java new file mode 100644 index 0000000..8606bb4 --- /dev/null +++ b/src/main/java/com/rabbitmqprac/global/annotation/SecurityUserCacheManager.java @@ -0,0 +1,19 @@ +package com.rabbitmqprac.global.annotation; + +import com.rabbitmqprac.global.consant.CacheManagerType; +import org.springframework.beans.factory.annotation.Qualifier; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, + ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier(CacheManagerType.SECURITY_USER) +public @interface SecurityUserCacheManager { +} + diff --git a/src/main/java/com/rabbitmqprac/global/consant/CacheManagerType.java b/src/main/java/com/rabbitmqprac/global/consant/CacheManagerType.java new file mode 100644 index 0000000..d9ba3c0 --- /dev/null +++ b/src/main/java/com/rabbitmqprac/global/consant/CacheManagerType.java @@ -0,0 +1,6 @@ +package com.rabbitmqprac.global.consant; + +public final class CacheManagerType { + public static final String SECURITY_USER = "securityUserCacheManager"; + public static final String OIDC = "oidcCacheManager"; +} diff --git a/src/main/java/com/rabbitmqprac/infra/oauth/client/GoogleOidcClient.java b/src/main/java/com/rabbitmqprac/infra/oauth/client/GoogleOidcClient.java index e5e9597..48d86d7 100644 --- a/src/main/java/com/rabbitmqprac/infra/oauth/client/GoogleOidcClient.java +++ b/src/main/java/com/rabbitmqprac/infra/oauth/client/GoogleOidcClient.java @@ -4,6 +4,7 @@ import com.rabbitmqprac.infra.oauth.dto.OidcPublicKeyRes; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; @@ -20,6 +21,7 @@ public class GoogleOidcClient implements OauthOidcClient { private String tokenUri; @Override + @Cacheable(value = "GoogleOauthOidc", cacheManager = "oidcCacheManager") public OidcPublicKeyRes getOidcPublicKey() { return webClient.get() .uri(jwksUri) diff --git a/src/main/java/com/rabbitmqprac/infra/oauth/client/KakaoOidcClient.java b/src/main/java/com/rabbitmqprac/infra/oauth/client/KakaoOidcClient.java index 45b5bc1..fd57063 100644 --- a/src/main/java/com/rabbitmqprac/infra/oauth/client/KakaoOidcClient.java +++ b/src/main/java/com/rabbitmqprac/infra/oauth/client/KakaoOidcClient.java @@ -4,6 +4,7 @@ import com.rabbitmqprac.infra.oauth.dto.OidcPublicKeyRes; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; @@ -21,6 +22,7 @@ public class KakaoOidcClient implements OauthOidcClient { private String tokenUri; @Override + @Cacheable(value = "KakaoOauthOidc", cacheManager = "oidcCacheManager") public OidcPublicKeyRes getOidcPublicKey() { return webClient.get() .uri(jwksUri) From 31d67a9f889372b59dd4c369460c65f21492f345 Mon Sep 17 00:00:00 2001 From: seungheonlee Date: Wed, 10 Sep 2025 20:07:11 +0900 Subject: [PATCH 2/3] =?UTF-8?q?rename:=20consant=20->=20constant=20?= =?UTF-8?q?=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=EB=AA=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/rabbitmqprac/global/annotation/OidcCacheManager.java | 2 +- .../global/annotation/SecurityUserCacheManager.java | 2 +- .../global/{consant => constant}/CacheManagerType.java | 2 +- .../rabbitmqprac/global/{consant => constant}/SessionType.java | 2 +- .../com/rabbitmqprac/global/interceptor/RequestInterceptor.java | 2 +- .../java/com/rabbitmqprac/global/util/RequestInfoExtractor.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename src/main/java/com/rabbitmqprac/global/{consant => constant}/CacheManagerType.java (80%) rename src/main/java/com/rabbitmqprac/global/{consant => constant}/SessionType.java (78%) diff --git a/src/main/java/com/rabbitmqprac/global/annotation/OidcCacheManager.java b/src/main/java/com/rabbitmqprac/global/annotation/OidcCacheManager.java index dba34ad..3fb9504 100644 --- a/src/main/java/com/rabbitmqprac/global/annotation/OidcCacheManager.java +++ b/src/main/java/com/rabbitmqprac/global/annotation/OidcCacheManager.java @@ -1,6 +1,6 @@ package com.rabbitmqprac.global.annotation; -import com.rabbitmqprac.global.consant.CacheManagerType; +import com.rabbitmqprac.global.constant.CacheManagerType; import org.springframework.beans.factory.annotation.Qualifier; import java.lang.annotation.Documented; diff --git a/src/main/java/com/rabbitmqprac/global/annotation/SecurityUserCacheManager.java b/src/main/java/com/rabbitmqprac/global/annotation/SecurityUserCacheManager.java index 8606bb4..dd7d79f 100644 --- a/src/main/java/com/rabbitmqprac/global/annotation/SecurityUserCacheManager.java +++ b/src/main/java/com/rabbitmqprac/global/annotation/SecurityUserCacheManager.java @@ -1,6 +1,6 @@ package com.rabbitmqprac.global.annotation; -import com.rabbitmqprac.global.consant.CacheManagerType; +import com.rabbitmqprac.global.constant.CacheManagerType; import org.springframework.beans.factory.annotation.Qualifier; import java.lang.annotation.Documented; diff --git a/src/main/java/com/rabbitmqprac/global/consant/CacheManagerType.java b/src/main/java/com/rabbitmqprac/global/constant/CacheManagerType.java similarity index 80% rename from src/main/java/com/rabbitmqprac/global/consant/CacheManagerType.java rename to src/main/java/com/rabbitmqprac/global/constant/CacheManagerType.java index d9ba3c0..1771477 100644 --- a/src/main/java/com/rabbitmqprac/global/consant/CacheManagerType.java +++ b/src/main/java/com/rabbitmqprac/global/constant/CacheManagerType.java @@ -1,4 +1,4 @@ -package com.rabbitmqprac.global.consant; +package com.rabbitmqprac.global.constant; public final class CacheManagerType { public static final String SECURITY_USER = "securityUserCacheManager"; diff --git a/src/main/java/com/rabbitmqprac/global/consant/SessionType.java b/src/main/java/com/rabbitmqprac/global/constant/SessionType.java similarity index 78% rename from src/main/java/com/rabbitmqprac/global/consant/SessionType.java rename to src/main/java/com/rabbitmqprac/global/constant/SessionType.java index e44e46b..a803651 100644 --- a/src/main/java/com/rabbitmqprac/global/consant/SessionType.java +++ b/src/main/java/com/rabbitmqprac/global/constant/SessionType.java @@ -1,4 +1,4 @@ -package com.rabbitmqprac.global.consant; +package com.rabbitmqprac.global.constant; public final class SessionType { public static final String START_TIME = "startTime"; diff --git a/src/main/java/com/rabbitmqprac/global/interceptor/RequestInterceptor.java b/src/main/java/com/rabbitmqprac/global/interceptor/RequestInterceptor.java index 0c9fe9b..7cad7aa 100644 --- a/src/main/java/com/rabbitmqprac/global/interceptor/RequestInterceptor.java +++ b/src/main/java/com/rabbitmqprac/global/interceptor/RequestInterceptor.java @@ -1,6 +1,6 @@ package com.rabbitmqprac.global.interceptor; -import com.rabbitmqprac.global.consant.SessionType; +import com.rabbitmqprac.global.constant.SessionType; import com.rabbitmqprac.global.util.RequestInfoExtractor; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/com/rabbitmqprac/global/util/RequestInfoExtractor.java b/src/main/java/com/rabbitmqprac/global/util/RequestInfoExtractor.java index 6d7dee0..0e6cc19 100644 --- a/src/main/java/com/rabbitmqprac/global/util/RequestInfoExtractor.java +++ b/src/main/java/com/rabbitmqprac/global/util/RequestInfoExtractor.java @@ -1,7 +1,7 @@ package com.rabbitmqprac.global.util; import com.rabbitmqprac.global.annotation.Util; -import com.rabbitmqprac.global.consant.SessionType; +import com.rabbitmqprac.global.constant.SessionType; import jakarta.servlet.http.HttpServletRequest; import org.apache.logging.log4j.util.Strings; import org.springframework.web.context.request.RequestAttributes; From 1229f1b90bd139929ceafa04eaa2234af234400c Mon Sep 17 00:00:00 2001 From: seungheonlee Date: Wed, 10 Sep 2025 20:10:31 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20redis=20=EC=BA=90=EC=8B=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - null 값 캐싱 비활성화 - 캐시 키 접두사 계산 방식 추가 --- src/main/java/com/rabbitmqprac/config/RedisCacheConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/rabbitmqprac/config/RedisCacheConfig.java b/src/main/java/com/rabbitmqprac/config/RedisCacheConfig.java index 052e9de..70d2021 100644 --- a/src/main/java/com/rabbitmqprac/config/RedisCacheConfig.java +++ b/src/main/java/com/rabbitmqprac/config/RedisCacheConfig.java @@ -94,6 +94,8 @@ public CacheManager securityUserCacheManager(@InfraRedisConnectionFactory RedisC @OidcCacheManager public CacheManager oidcCacheManager(@InfraRedisConnectionFactory RedisConnectionFactory cf) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .disableCachingNullValues() + .computePrefixWith(CacheKeyPrefix.simple()) .serializeKeysWith( RedisSerializationContext.SerializationPair.fromSerializer( new StringRedisSerializer()