From fcb583240343be5b798ca894ec21564548274d70 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 24 Jan 2025 07:02:51 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=84=B8=ED=8C=85=20-=20#147?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cakey-api/build.gradle | 3 +- .../cakey/cake/controller/CakeController.java | 1 + .../filter/RequiredAuthenticationFilter.java | 4 +- .../java/com/cakey/config/CacheConfig.java | 44 --------- .../store/controller/StoreController.java | 4 +- .../com/cakey/user/service/UserService.java | 9 +- .../src/main/resources/logback-spring.xml | 4 +- cakey-auth/build.gradle | 4 + .../main/java/com/cakey/jwt/CacheConfig.java | 34 +++++++ .../java/com/cakey/jwt/auth/JwtProvider.java | 4 + .../java/com/cakey/TestConfiguration.java | 13 +++ .../com/cakey/jwt/auth/JwtGeneratorTest.java | 98 +++++++++++++++++++ 12 files changed, 167 insertions(+), 55 deletions(-) delete mode 100644 cakey-api/src/main/java/com/cakey/config/CacheConfig.java create mode 100644 cakey-auth/src/main/java/com/cakey/jwt/CacheConfig.java create mode 100644 cakey-auth/src/test/java/com/cakey/TestConfiguration.java create mode 100644 cakey-auth/src/test/java/com/cakey/jwt/auth/JwtGeneratorTest.java diff --git a/cakey-api/build.gradle b/cakey-api/build.gradle index 0963c0d..f45521d 100644 --- a/cakey-api/build.gradle +++ b/cakey-api/build.gradle @@ -24,8 +24,7 @@ dependencies { implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-cache' - implementation 'com.github.ben-manes.caffeine:caffeine:3.1.1' + } // 실행가능한 jar로 생성하는 옵션, main이 없는 라이브러리에서는 false로 비활성화함 diff --git a/cakey-api/src/main/java/com/cakey/cake/controller/CakeController.java b/cakey-api/src/main/java/com/cakey/cake/controller/CakeController.java index 5611efe..f17d2aa 100644 --- a/cakey-api/src/main/java/com/cakey/cake/controller/CakeController.java +++ b/cakey-api/src/main/java/com/cakey/cake/controller/CakeController.java @@ -10,6 +10,7 @@ import com.cakey.store.domain.Station; import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; diff --git a/cakey-api/src/main/java/com/cakey/common/filter/RequiredAuthenticationFilter.java b/cakey-api/src/main/java/com/cakey/common/filter/RequiredAuthenticationFilter.java index 0db2cdc..9f2977c 100644 --- a/cakey-api/src/main/java/com/cakey/common/filter/RequiredAuthenticationFilter.java +++ b/cakey-api/src/main/java/com/cakey/common/filter/RequiredAuthenticationFilter.java @@ -73,6 +73,9 @@ protected void doFilterInternal( SecurityContextHolder.getContext().setAuthentication(new UserAuthentication(userId, null, null)); filterChain.doFilter(request, response); // 다음 필터로 요청 전달 } catch (Exception e) { + log.error("--------------------쿠키 에러------------------------"); + log.error(e.getMessage()); + // 예외 발생 시 JSON 응답 생성 final ErrorBaseCode errorCode = ErrorBaseCode.UNAUTHORIZED; @@ -80,7 +83,6 @@ protected void doFilterInternal( response.setCharacterEncoding(Constants.CHARACTER_TYPE); response.setStatus(errorCode.getHttpStatus().value()); // HTTP 상태 코드 401 설정 - log.error("--------------------쿠키 없음------------------------"); //todo: 추후 삭제(테스트용) // `ApiResponseUtil.failure`를 이용해 응답 작성 final PrintWriter writer = response.getWriter(); writer.write(objectMapper.writeValueAsString( diff --git a/cakey-api/src/main/java/com/cakey/config/CacheConfig.java b/cakey-api/src/main/java/com/cakey/config/CacheConfig.java deleted file mode 100644 index 05231d0..0000000 --- a/cakey-api/src/main/java/com/cakey/config/CacheConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.cakey.config; - -import com.github.benmanes.caffeine.cache.Caffeine; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.cache.caffeine.CaffeineCache; -import org.springframework.cache.caffeine.CaffeineCacheManager; -import org.springframework.cache.support.SimpleCacheManager; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@EnableCaching -@Configuration -public class CacheConfig { - - @Bean - public CacheManager cacheManager() { - SimpleCacheManager simpleCacheManager = new SimpleCacheManager(); - - List cacheList = new ArrayList(); - - - cacheList.add( - // ABC - new CaffeineCache("refresh", - Caffeine.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .initialCapacity(200) - .softValues() - .maximumSize(1000) - .recordStats() - .build() - ) - ); - - simpleCacheManager.setCaches(cacheList); - - return simpleCacheManager; - } -} diff --git a/cakey-api/src/main/java/com/cakey/store/controller/StoreController.java b/cakey-api/src/main/java/com/cakey/store/controller/StoreController.java index 5cbc794..acf8fc3 100644 --- a/cakey-api/src/main/java/com/cakey/store/controller/StoreController.java +++ b/cakey-api/src/main/java/com/cakey/store/controller/StoreController.java @@ -10,6 +10,7 @@ import com.cakey.store.service.StoreService; import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -17,9 +18,9 @@ @RestController @Validated @RequiredArgsConstructor +@Slf4j @RequestMapping("/api/v1/store") public class StoreController { - private final StoreService storeService; //스토어 좌표 리스트 조회 @@ -71,6 +72,7 @@ public ResponseEntity> getStoreInfoListByStationAndLatest( //전체 지하철역 조회 @GetMapping("/station") public ResponseEntity> getAllStation() { + log.error("----------------------testtestsetset---------------"); // todo: 추후 삭제( 로그 테스트용) return ApiResponseUtil.success( SuccessCode.OK, storeService.getAllStation()); diff --git a/cakey-api/src/main/java/com/cakey/user/service/UserService.java b/cakey-api/src/main/java/com/cakey/user/service/UserService.java index 923884a..ea65f71 100644 --- a/cakey-api/src/main/java/com/cakey/user/service/UserService.java +++ b/cakey-api/src/main/java/com/cakey/user/service/UserService.java @@ -25,6 +25,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cloud.openfeign.aot.FeignChildContextInitializer; import org.springframework.http.ResponseCookie; import org.springframework.stereotype.Service; @@ -41,6 +42,7 @@ public class UserService { private final static String REFRESH_TOKEN = "refreshToken"; private final JwtProvider jwtProvider; private final UserRetriever userRetriever; + private final FeignChildContextInitializer feignChildContextInitializer; public LoginSuccessRes login( @@ -87,7 +89,7 @@ public LoginSuccessRes login( } else { //전에 이미 우리 유저 //리프레시 토큰 캐시 삭제 - deleteRefreshToken(userId); + jwtProvider.deleteRefreshToken(userId); final Token newToken = jwtProvider.issueToken(userId); @@ -116,12 +118,9 @@ public void logout(final long userId, final HttpServletResponse response) { } deleteAccessCookie(response); deleteRefreshCookie(response); - deleteRefreshToken(userId); + jwtProvider.deleteRefreshToken(userId); } - @CacheEvict(value = "refresh") - public void deleteRefreshToken(final long userId) { } - //accessToken 쿠키 삭제 public void deleteAccessCookie(HttpServletResponse response) { ResponseCookie accessCookie = ResponseCookie.from(ACCESS_TOKEN, "") diff --git a/cakey-api/src/main/resources/logback-spring.xml b/cakey-api/src/main/resources/logback-spring.xml index e589409..b4145e1 100644 --- a/cakey-api/src/main/resources/logback-spring.xml +++ b/cakey-api/src/main/resources/logback-spring.xml @@ -19,10 +19,10 @@ %d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %logger{0} - %msg%n - /home/ubuntu/CAKEY-ERROR-LOG.log + */home/ubuntu/CAKEY-ERROR-LOG.log - /home/ubuntu/CAKEY-ERROR-LOG-%d{yyyy-MM-dd}-%i-log.zip + */home/ubuntu/CAKEY-ERROR-LOG-%d{yyyy-MM-dd}-%i-log.zip 50MB 30 3GB diff --git a/cakey-auth/build.gradle b/cakey-auth/build.gradle index 94bead8..83a127a 100644 --- a/cakey-auth/build.gradle +++ b/cakey-auth/build.gradle @@ -11,4 +11,8 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.0' //feign implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' //oauth2 + + //cache + implementation 'org.springframework.boot:spring-boot-starter-cache' + implementation 'com.github.ben-manes.caffeine:caffeine:3.1.1' } diff --git a/cakey-auth/src/main/java/com/cakey/jwt/CacheConfig.java b/cakey-auth/src/main/java/com/cakey/jwt/CacheConfig.java new file mode 100644 index 0000000..60db9ba --- /dev/null +++ b/cakey-auth/src/main/java/com/cakey/jwt/CacheConfig.java @@ -0,0 +1,34 @@ +package com.cakey.jwt; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import com.github.benmanes.caffeine.cache.Caffeine; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.cache.support.SimpleCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@EnableCaching +@Configuration +public class CacheConfig { + + @Bean + public CacheManager cacheManager() { + // CaffeineCacheManager를 사용해 동적으로 캐시 이름을 생성 + CaffeineCacheManager cacheManager = new CaffeineCacheManager(); + + // 모든 캐시에 동일한 기본 설정을 적용 + cacheManager.setCaffeine(Caffeine.newBuilder() + .initialCapacity(100) // 초기 용량 + .maximumSize(500) // 최대 캐시 크기 + .recordStats()); // 캐시 통계 활성화 + + return cacheManager; + } +} diff --git a/cakey-auth/src/main/java/com/cakey/jwt/auth/JwtProvider.java b/cakey-auth/src/main/java/com/cakey/jwt/auth/JwtProvider.java index c8f8980..a6a6738 100644 --- a/cakey-auth/src/main/java/com/cakey/jwt/auth/JwtProvider.java +++ b/cakey-auth/src/main/java/com/cakey/jwt/auth/JwtProvider.java @@ -6,6 +6,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Component; @RequiredArgsConstructor @@ -28,6 +29,9 @@ public String generateRefreshToken(final long userId) { return jwtGenerator.generateRefreshToken(userId); } + @CacheEvict(value = "refresh") + public void deleteRefreshToken(final long userId) { } + public long getUserIdFromSubject(final String token) { Jws jws = jwtGenerator.parseToken(token); String subject = jws.getBody().getSubject(); diff --git a/cakey-auth/src/test/java/com/cakey/TestConfiguration.java b/cakey-auth/src/test/java/com/cakey/TestConfiguration.java new file mode 100644 index 0000000..6f151ae --- /dev/null +++ b/cakey-auth/src/test/java/com/cakey/TestConfiguration.java @@ -0,0 +1,13 @@ +package com.cakey; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableCaching +@SpringBootApplication +@ComponentScan(basePackages = "com.cakey.jwt") // 실제 패키지 경로로 수정 +public class TestConfiguration { +} diff --git a/cakey-auth/src/test/java/com/cakey/jwt/auth/JwtGeneratorTest.java b/cakey-auth/src/test/java/com/cakey/jwt/auth/JwtGeneratorTest.java new file mode 100644 index 0000000..6ac19ba --- /dev/null +++ b/cakey-auth/src/test/java/com/cakey/jwt/auth/JwtGeneratorTest.java @@ -0,0 +1,98 @@ +package com.cakey.jwt.auth; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + + +@SpringBootTest(properties = { + "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration" +}) +@TestPropertySource(properties = { + "jwt.secret=testasdfasdfasdfsecretfdadfsdfasdfasdfasdfasdf", + "jwt.accessTokenExpirationTime=3600", + "jwt.refreshTokenExpirationTime=604800" +}) +@ComponentScan(basePackages = "com.cakey.jwt.auth") +class JwtGeneratorTest { + + @Autowired + private JwtProperties jwtProperties; + + @Autowired + private JwtGenerator jwtGenerator; + + @Autowired + private CacheManager cacheManager; + + @Autowired + private JwtProvider jwtProvider; + + @Test + @DisplayName("리프레시 토큰 캐시에 등록") + void generateRefreshToken() { + // Given + // 캐시 초기화 + cacheManager.getCache("refresh").clear(); + + long firstId = 123L; + long secondId = 456L; + + final String token1 = jwtGenerator.generateRefreshToken(firstId); + System.out.println("token 1 : " + token1); + + final String token2 = jwtGenerator.generateRefreshToken(secondId); + System.out.println("token 2 : " + token2); + + // When + final Cache cache = cacheManager.getCache("refresh"); + + // Then + // 캐시에서 값 가져오기 + final String firstCachedToken = cache.get(firstId, String.class); + assertThat(firstCachedToken).isNotNull(); + System.out.println("token 1: " + firstCachedToken); + assertThat(firstCachedToken).isEqualTo(token1); // 캐시된 값이 첫 번째 토큰과 동일해야 함 + + final String secondCachedToken = cache.get(firstId, String.class); + assertThat(secondCachedToken).isNotNull(); + System.out.println("token 2: " + secondCachedToken); + assertThat(secondCachedToken).isEqualTo(token1); // 캐시된 값이 두 번째 토큰과 동일해야 함 + } + + @Test + @DisplayName("리프레시 토큰 캐시 삭제") + void deleteRefreshToken() { + //Given + long userId = 123L; + + //When + jwtProvider.deleteRefreshToken(userId); + + //Then + Cache cache = cacheManager.getCache("refresh"); + String cachedValue = cache.get(userId, String.class); + assertThat(cachedValue).isNull(); // 키에 해당하는 값이 삭제되었는지 확인 + } + + @Test + @DisplayName("초기화 테스트") + void reset() { + // 캐시 초기화 + cacheManager.getCache("refresh").clear(); + + Cache cache = cacheManager.getCache("refresh"); + + assertThat(cache).isNull(); + } +} From 9d7eadd3353ace33e80924eb770d2d39f2fdb962 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 24 Jan 2025 18:07:52 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=EC=BA=90=EC=8B=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?-=20#147?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/cakey/jwt/CacheConfig.java | 17 +++---- .../com/cakey/jwt/auth/JwtGeneratorTest.java | 50 ++++++++++--------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/cakey-auth/src/main/java/com/cakey/jwt/CacheConfig.java b/cakey-auth/src/main/java/com/cakey/jwt/CacheConfig.java index 60db9ba..6a939d9 100644 --- a/cakey-auth/src/main/java/com/cakey/jwt/CacheConfig.java +++ b/cakey-auth/src/main/java/com/cakey/jwt/CacheConfig.java @@ -20,15 +20,14 @@ public class CacheConfig { @Bean public CacheManager cacheManager() { - // CaffeineCacheManager를 사용해 동적으로 캐시 이름을 생성 - CaffeineCacheManager cacheManager = new CaffeineCacheManager(); - - // 모든 캐시에 동일한 기본 설정을 적용 - cacheManager.setCaffeine(Caffeine.newBuilder() - .initialCapacity(100) // 초기 용량 - .maximumSize(500) // 최대 캐시 크기 - .recordStats()); // 캐시 통계 활성화 - + SimpleCacheManager cacheManager = new SimpleCacheManager(); + List caches = new ArrayList<>(); + caches.add(new CaffeineCache("refresh", Caffeine.newBuilder() + .initialCapacity(100) + .maximumSize(500) + .recordStats() + .build())); + cacheManager.setCaches(caches); return cacheManager; } } diff --git a/cakey-auth/src/test/java/com/cakey/jwt/auth/JwtGeneratorTest.java b/cakey-auth/src/test/java/com/cakey/jwt/auth/JwtGeneratorTest.java index 6ac19ba..cf4aee8 100644 --- a/cakey-auth/src/test/java/com/cakey/jwt/auth/JwtGeneratorTest.java +++ b/cakey-auth/src/test/java/com/cakey/jwt/auth/JwtGeneratorTest.java @@ -1,34 +1,30 @@ package com.cakey.jwt.auth; -import org.junit.jupiter.api.BeforeEach; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; -import org.springframework.context.annotation.ComponentScan; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.test.context.TestPropertySource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.*; @SpringBootTest(properties = { "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration" }) @TestPropertySource(properties = { - "jwt.secret=testasdfasdfasdfsecretfdadfsdfasdfasdfasdfasdf", + "jwt.secret=testasdfasdfasddfsecretfdadfsdfasdfasdfasdfasdf", "jwt.accessTokenExpirationTime=3600", "jwt.refreshTokenExpirationTime=604800" }) -@ComponentScan(basePackages = "com.cakey.jwt.auth") +//@ComponentScan(basePackages = "com.cakey.jwt.auth") class JwtGeneratorTest { - @Autowired - private JwtProperties jwtProperties; - @Autowired private JwtGenerator jwtGenerator; @@ -43,7 +39,12 @@ class JwtGeneratorTest { void generateRefreshToken() { // Given // 캐시 초기화 - cacheManager.getCache("refresh").clear(); + cacheManager.getCacheNames().forEach(cacheName -> { + Cache cache = cacheManager.getCache(cacheName); + if (cache != null) { + cache.clear(); + } + }); long firstId = 123L; long secondId = 456L; @@ -64,35 +65,36 @@ void generateRefreshToken() { System.out.println("token 1: " + firstCachedToken); assertThat(firstCachedToken).isEqualTo(token1); // 캐시된 값이 첫 번째 토큰과 동일해야 함 - final String secondCachedToken = cache.get(firstId, String.class); + final String secondCachedToken = cache.get(secondId, String.class); assertThat(secondCachedToken).isNotNull(); System.out.println("token 2: " + secondCachedToken); - assertThat(secondCachedToken).isEqualTo(token1); // 캐시된 값이 두 번째 토큰과 동일해야 함 + assertThat(secondCachedToken).isEqualTo(token2); // 캐시된 값이 두 번째 토큰과 동일해야 함 } @Test @DisplayName("리프레시 토큰 캐시 삭제") void deleteRefreshToken() { //Given - long userId = 123L; + final long userId = 123L; + + Cache cache = cacheManager.getCache("refresh"); + System.out.println(cache); + assertThat(cache).isNotNull(); + + String cachedValue = cache.get(userId, String.class); + System.out.println(cachedValue); + assertThat(cachedValue).isNotNull(); //When jwtProvider.deleteRefreshToken(userId); //Then - Cache cache = cacheManager.getCache("refresh"); - String cachedValue = cache.get(userId, String.class); - assertThat(cachedValue).isNull(); // 키에 해당하는 값이 삭제되었는지 확인 - } - - @Test - @DisplayName("초기화 테스트") - void reset() { - // 캐시 초기화 - cacheManager.getCache("refresh").clear(); + cache = cacheManager.getCache("refresh"); + System.out.println(cache); - Cache cache = cacheManager.getCache("refresh"); + cachedValue = cache.get(userId, String.class); + System.out.println(cachedValue); - assertThat(cache).isNull(); + Assertions.assertThat(cachedValue).isNull(); // 키에 해당하는 값이 삭제되었는지 확인 } }