From 210ae525ccf91bc64f047301e65c89f49443aa03 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 14 Jan 2025 22:19:28 +0900 Subject: [PATCH 01/21] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=A0=9C=EA=B3=B5=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TokenService > TokenProvider - 비지니스 로직이 아니라 기능만 제공하는 것이라 Provider가 더 적절하다고 판단함 --- .../auth/service/AuthService.java | 8 +++---- .../auth/service/SignInService.java | 14 ++++++------ .../auth/service/SignUpService.java | 12 +++++----- .../security/JwtAuthenticationFilter.java | 8 +++---- .../{TokenService.java => TokenProvider.java} | 2 +- .../e2e/ApplicantsQueryTest.java | 22 +++++++++---------- .../solidconnection/e2e/MyPageTest.java | 10 ++++----- .../solidconnection/e2e/MyPageUpdateTest.java | 10 ++++----- .../solidconnection/e2e/SignUpTest.java | 16 +++++++------- .../e2e/UniversityDetailTest.java | 10 ++++----- .../e2e/UniversityLikeTest.java | 10 ++++----- .../e2e/UniversityRecommendTest.java | 10 ++++----- .../e2e/UniversitySearchTest.java | 10 ++++----- 13 files changed, 71 insertions(+), 71 deletions(-) rename src/main/java/com/example/solidconnection/config/token/{TokenService.java => TokenProvider.java} (98%) diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index caf78074d..de5491324 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -2,7 +2,7 @@ import com.example.solidconnection.auth.dto.ReissueResponse; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -24,7 +24,7 @@ public class AuthService { private final RedisTemplate redisTemplate; - private final TokenService tokenService; + private final TokenProvider tokenProvider; private final SiteUserRepository siteUserRepository; /* @@ -68,8 +68,8 @@ public ReissueResponse reissue(String email) { throw new CustomException(REFRESH_TOKEN_EXPIRED); } // 액세스 토큰 재발급 - String newAccessToken = tokenService.generateToken(email, TokenType.ACCESS); - tokenService.saveToken(newAccessToken, TokenType.ACCESS); + String newAccessToken = tokenProvider.generateToken(email, TokenType.ACCESS); + tokenProvider.saveToken(newAccessToken, TokenType.ACCESS); return new ReissueResponse(newAccessToken); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/SignInService.java b/src/main/java/com/example/solidconnection/auth/service/SignInService.java index f6adda20d..acfcfe8f5 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignInService.java @@ -6,7 +6,7 @@ import com.example.solidconnection.auth.dto.kakao.KakaoCodeRequest; import com.example.solidconnection.auth.dto.kakao.KakaoOauthResponse; import com.example.solidconnection.auth.dto.kakao.KakaoUserInfoDto; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -18,7 +18,7 @@ @Service public class SignInService { - private final TokenService tokenService; + private final TokenProvider tokenProvider; private final SiteUserRepository siteUserRepository; private final KakaoOAuthClient kakaoOAuthClient; @@ -58,15 +58,15 @@ private void resetQuitedAt(String email) { } private SignInResponse getSignInInfo(String email) { - String accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); + String accessToken = tokenProvider.generateToken(email, TokenType.ACCESS); + String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH); + tokenProvider.saveToken(refreshToken, TokenType.REFRESH); return new SignInResponse(true, accessToken, refreshToken); } private FirstAccessResponse getFirstAccessInfo(KakaoUserInfoDto kakaoUserInfoDto) { - String kakaoOauthToken = tokenService.generateToken(kakaoUserInfoDto.kakaoAccountDto().email(), TokenType.KAKAO_OAUTH); - tokenService.saveToken(kakaoOauthToken, TokenType.KAKAO_OAUTH); + String kakaoOauthToken = tokenProvider.generateToken(kakaoUserInfoDto.kakaoAccountDto().email(), TokenType.KAKAO_OAUTH); + tokenProvider.saveToken(kakaoOauthToken, TokenType.KAKAO_OAUTH); return FirstAccessResponse.of(kakaoUserInfoDto, kakaoOauthToken); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java index f10f40dbd..ebae98a41 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java @@ -2,7 +2,7 @@ import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.dto.SignUpResponse; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.config.token.TokenValidator; import com.example.solidconnection.custom.exception.CustomException; @@ -29,7 +29,7 @@ public class SignUpService { private final TokenValidator tokenValidator; - private final TokenService tokenService; + private final TokenProvider tokenProvider; private final SiteUserRepository siteUserRepository; private final RegionRepository regionRepository; private final InterestedRegionRepository interestedRegionRepository; @@ -51,7 +51,7 @@ public class SignUpService { public SignUpResponse signUp(SignUpRequest signUpRequest) { // 검증 tokenValidator.validateKakaoToken(signUpRequest.kakaoOauthToken()); - String email = tokenService.getEmail(signUpRequest.kakaoOauthToken()); + String email = tokenProvider.getEmail(signUpRequest.kakaoOauthToken()); validateNicknameDuplicated(signUpRequest.nickname()); validateUserNotDuplicated(email); @@ -64,9 +64,9 @@ public SignUpResponse signUp(SignUpRequest signUpRequest) { saveInterestedCountry(signUpRequest, savedSiteUser); // 토큰 발급 - String accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); + String accessToken = tokenProvider.generateToken(email, TokenType.ACCESS); + String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH); + tokenProvider.saveToken(refreshToken, TokenType.REFRESH); return new SignUpResponse(accessToken, refreshToken); } diff --git a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java index a618bec04..44a252c2e 100644 --- a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java @@ -1,6 +1,6 @@ package com.example.solidconnection.config.security; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenValidator; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.custom.exception.JwtExpiredTokenException; @@ -30,7 +30,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { public static final String TOKEN_HEADER = "Authorization"; public static final String TOKEN_PREFIX = "Bearer "; - private final TokenService tokenService; + private final TokenProvider tokenProvider; private final TokenValidator tokenValidator; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @@ -54,7 +54,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse try { String requestURI = request.getRequestURI(); if (requestURI.equals("/auth/reissue")) { - Authentication auth = this.tokenService.getAuthentication(token); + Authentication auth = this.tokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(auth); filterChain.doFilter(request, response); return; @@ -63,7 +63,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } catch (ExpiredJwtException e) { throw new JwtExpiredTokenException(ACCESS_TOKEN_EXPIRED.getMessage()); } - Authentication auth = this.tokenService.getAuthentication(token); // 토큰에서 인증 정보 가져옴 + Authentication auth = this.tokenProvider.getAuthentication(token); // 토큰에서 인증 정보 가져옴 SecurityContextHolder.getContext().setAuthentication(auth);// 인증 정보를 보안 컨텍스트에 설정 } } catch (JwtExpiredTokenException e) { diff --git a/src/main/java/com/example/solidconnection/config/token/TokenService.java b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java similarity index 98% rename from src/main/java/com/example/solidconnection/config/token/TokenService.java rename to src/main/java/com/example/solidconnection/config/token/TokenProvider.java index fc9ccea31..27e59656f 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenService.java +++ b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java @@ -18,7 +18,7 @@ @RequiredArgsConstructor @Component -public class TokenService { +public class TokenProvider { private final RedisTemplate redisTemplate; private final CustomUserDetailsService customUserDetailsService; diff --git a/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java b/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java index 2f69d6cf7..8caf6c204 100644 --- a/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java +++ b/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java @@ -7,7 +7,7 @@ import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.UniversityApplicantsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -36,7 +36,7 @@ class ApplicantsQueryTest extends UniversityDataSetUpEndToEndTest { ApplicationRepository applicationRepository; @Autowired - TokenService tokenService; + TokenProvider tokenProvider; private String accessToken; private String adminAccessToken; @@ -60,17 +60,17 @@ public void setUpUserAndToken() { SiteUser siteUser = siteUserRepository.save(createSiteUserByEmail(email)); // setUp - 엑세스 토큰 생성과 리프레시 토큰 생성 및 저장 - accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); + accessToken = tokenProvider.generateToken(email, TokenType.ACCESS); + String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH); + tokenProvider.saveToken(refreshToken, TokenType.REFRESH); - adminAccessToken = tokenService.generateToken("email5", TokenType.ACCESS); - String adminRefreshToken = tokenService.generateToken("email5", TokenType.REFRESH); - tokenService.saveToken(adminRefreshToken, TokenType.REFRESH); + adminAccessToken = tokenProvider.generateToken("email5", TokenType.ACCESS); + String adminRefreshToken = tokenProvider.generateToken("email5", TokenType.REFRESH); + tokenProvider.saveToken(adminRefreshToken, TokenType.REFRESH); - user6AccessToken = tokenService.generateToken("email6", TokenType.ACCESS); - String user6RefreshToken = tokenService.generateToken("email6", TokenType.REFRESH); - tokenService.saveToken(user6RefreshToken, TokenType.REFRESH); + user6AccessToken = tokenProvider.generateToken("email6", TokenType.ACCESS); + String user6RefreshToken = tokenProvider.generateToken("email6", TokenType.REFRESH); + tokenProvider.saveToken(user6RefreshToken, TokenType.REFRESH); // setUp - 사용자 정보 저장 SiteUser 사용자1 = siteUserRepository.save(createSiteUserByEmail("email1")); diff --git a/src/test/java/com/example/solidconnection/e2e/MyPageTest.java b/src/test/java/com/example/solidconnection/e2e/MyPageTest.java index 059e00cde..66b4d5b7d 100644 --- a/src/test/java/com/example/solidconnection/e2e/MyPageTest.java +++ b/src/test/java/com/example/solidconnection/e2e/MyPageTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; @@ -23,7 +23,7 @@ class MyPageTest extends BaseEndToEndTest { @Autowired private SiteUserRepository siteUserRepository; @Autowired - private TokenService tokenService; + private TokenProvider tokenProvider; private String accessToken; @BeforeEach @@ -32,9 +32,9 @@ public void setUpUserAndToken() { siteUserRepository.save(createSiteUserByEmail(email)); // setUp - 엑세스 토큰 생성과 리프레시 토큰 생성 및 저장 - accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); + accessToken = tokenProvider.generateToken(email, TokenType.ACCESS); + String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH); + tokenProvider.saveToken(refreshToken, TokenType.REFRESH); } @Test diff --git a/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java b/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java index cb058fe3a..7be4ba4b9 100644 --- a/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java +++ b/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.custom.response.ErrorResponse; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -31,7 +31,7 @@ class MyPageUpdateTest extends BaseEndToEndTest { private SiteUserRepository siteUserRepository; @Autowired - private TokenService tokenService; + private TokenProvider tokenProvider; private String accessToken; @@ -46,9 +46,9 @@ public void setUpUserAndToken() { siteUserRepository.save(siteUser); // setUp - 엑세스 토큰 생성과 리프레시 토큰 생성 및 저장 - accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); + accessToken = tokenProvider.generateToken(email, TokenType.ACCESS); + String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH); + tokenProvider.saveToken(refreshToken, TokenType.REFRESH); } @Test diff --git a/src/test/java/com/example/solidconnection/e2e/SignUpTest.java b/src/test/java/com/example/solidconnection/e2e/SignUpTest.java index eff3e54b5..f4ca9e061 100644 --- a/src/test/java/com/example/solidconnection/e2e/SignUpTest.java +++ b/src/test/java/com/example/solidconnection/e2e/SignUpTest.java @@ -2,7 +2,7 @@ import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.dto.SignUpResponse; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.custom.response.ErrorResponse; import com.example.solidconnection.entity.Country; @@ -54,7 +54,7 @@ class SignUpTest extends BaseEndToEndTest { InterestedCountyRepository interestedCountyRepository; @Autowired - TokenService tokenService; + TokenProvider tokenProvider; @Autowired RedisTemplate redisTemplate; @@ -69,8 +69,8 @@ class SignUpTest extends BaseEndToEndTest { // setup - 카카오 토큰 발급 String email = "email@email.com"; - String generatedKakaoToken = tokenService.generateToken(email, TokenType.KAKAO_OAUTH); - tokenService.saveToken(generatedKakaoToken, TokenType.KAKAO_OAUTH); + String generatedKakaoToken = tokenProvider.generateToken(email, TokenType.KAKAO_OAUTH); + tokenProvider.saveToken(generatedKakaoToken, TokenType.KAKAO_OAUTH); // request - body 생성 및 요청 List interestedRegionNames = List.of("유럽"); @@ -122,8 +122,8 @@ class SignUpTest extends BaseEndToEndTest { // setup - 카카오 토큰 발급 String email = "email@email.com"; - String generatedKakaoToken = tokenService.generateToken(email, TokenType.KAKAO_OAUTH); - tokenService.saveToken(generatedKakaoToken, TokenType.KAKAO_OAUTH); + String generatedKakaoToken = tokenProvider.generateToken(email, TokenType.KAKAO_OAUTH); + tokenProvider.saveToken(generatedKakaoToken, TokenType.KAKAO_OAUTH); // request - body 생성 및 요청 SignUpRequest signUpRequest = new SignUpRequest(generatedKakaoToken, null, null, @@ -148,8 +148,8 @@ class SignUpTest extends BaseEndToEndTest { siteUserRepository.save(alreadyExistUser); // setup - 카카오 토큰 발급 - String generatedKakaoToken = tokenService.generateToken(alreadyExistEmail, TokenType.KAKAO_OAUTH); - tokenService.saveToken(generatedKakaoToken, TokenType.KAKAO_OAUTH); + String generatedKakaoToken = tokenProvider.generateToken(alreadyExistEmail, TokenType.KAKAO_OAUTH); + tokenProvider.saveToken(generatedKakaoToken, TokenType.KAKAO_OAUTH); // request - body 생성 및 요청 SignUpRequest signUpRequest = new SignUpRequest(generatedKakaoToken, null, null, diff --git a/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java b/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java index dc8401700..d4dbb533b 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -24,7 +24,7 @@ class UniversityDetailTest extends UniversityDataSetUpEndToEndTest { private SiteUserRepository siteUserRepository; @Autowired - private TokenService tokenService; + private TokenProvider tokenProvider; private String accessToken; @@ -36,9 +36,9 @@ public void setUpUserAndToken() { siteUserRepository.save(siteUser); // setUp - 엑세스 토큰 생성과 리프레시 토큰 생성 및 저장 - accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); + accessToken = tokenProvider.generateToken(email, TokenType.ACCESS); + String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH); + tokenProvider.saveToken(refreshToken, TokenType.REFRESH); } @Test diff --git a/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java b/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java index 37f922e4e..ef6467d47 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; @@ -45,7 +45,7 @@ class UniversityLikeTest extends UniversityDataSetUpEndToEndTest { private LikedUniversityRepository likedUniversityRepository; @Autowired - private TokenService tokenService; + private TokenProvider tokenProvider; private String accessToken; private SiteUser siteUser; @@ -57,9 +57,9 @@ public void setUpUserAndToken() { siteUserRepository.save(siteUser); // setUp - 엑세스 토큰 생성과 리프레시 토큰 생성 및 저장 - accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); + accessToken = tokenProvider.generateToken(email, TokenType.ACCESS); + String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH); + tokenProvider.saveToken(refreshToken, TokenType.REFRESH); } @Test diff --git a/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java b/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java index ee46733a1..32899829a 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.entity.InterestedCountry; import com.example.solidconnection.entity.InterestedRegion; @@ -38,7 +38,7 @@ class UniversityRecommendTest extends UniversityDataSetUpEndToEndTest { private InterestedCountyRepository interestedCountyRepository; @Autowired - private TokenService tokenService; + private TokenProvider tokenProvider; @Autowired private GeneralRecommendUniversities generalRecommendUniversities; @@ -54,9 +54,9 @@ void setUp() { generalRecommendUniversities.init(); // setUp - 엑세스 토큰 생성과 리프레시 토큰 생성 및 저장 - accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); + accessToken = tokenProvider.generateToken(email, TokenType.ACCESS); + String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH); + tokenProvider.saveToken(refreshToken, TokenType.REFRESH); } @Test diff --git a/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java b/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java index 1187fb0ad..c6ddd37be 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenService; +import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; @@ -33,7 +33,7 @@ class UniversitySearchTest extends UniversityDataSetUpEndToEndTest { private LikedUniversityRepository likedUniversityRepository; @Autowired - private TokenService tokenService; + private TokenProvider tokenProvider; private String accessToken; private SiteUser siteUser; @@ -45,9 +45,9 @@ public void setUpUserAndToken() { siteUserRepository.save(siteUser); // setUp - 엑세스 토큰 생성과 리프레시 토큰 생성 및 저장 - accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); + accessToken = tokenProvider.generateToken(email, TokenType.ACCESS); + String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH); + tokenProvider.saveToken(refreshToken, TokenType.REFRESH); } @Test From ee49cfe5176d0da433e5c1d3285dfb0de83f8c9b Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 14 Jan 2025 22:22:18 +0900 Subject: [PATCH 02/21] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=A0=91=EB=91=90=EC=82=AC=20=EC=B6=94=EA=B0=80=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD,=20static?= =?UTF-8?q?=20import=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/AuthService.java | 13 +++++++------ .../config/token/TokenProvider.java | 2 +- .../solidconnection/config/token/TokenType.java | 8 ++++---- .../config/token/TokenValidator.java | 16 +++++++++------- .../example/solidconnection/e2e/SignInTest.java | 9 +++++---- .../example/solidconnection/e2e/SignUpTest.java | 17 +++++++++-------- 6 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index de5491324..06c392c10 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -3,7 +3,6 @@ import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -16,6 +15,8 @@ import java.time.LocalDate; import java.util.concurrent.TimeUnit; +import static com.example.solidconnection.config.token.TokenType.ACCESS; +import static com.example.solidconnection.config.token.TokenType.REFRESH; import static com.example.solidconnection.config.token.TokenValidator.SIGN_OUT_VALUE; import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; @@ -36,9 +37,9 @@ public class AuthService { * */ public void signOut(String email) { redisTemplate.opsForValue().set( - TokenType.REFRESH.addTokenPrefixToSubject(email), + REFRESH.addPrefixToSubject(email), SIGN_OUT_VALUE, - TokenType.REFRESH.getExpireTime(), + REFRESH.getExpireTime(), TimeUnit.MILLISECONDS ); } @@ -62,14 +63,14 @@ public void quit(String email) { * */ public ReissueResponse reissue(String email) { // 리프레시 토큰 만료 확인 - String refreshTokenKey = TokenType.REFRESH.addTokenPrefixToSubject(email); + String refreshTokenKey = REFRESH.addPrefixToSubject(email); String refreshToken = redisTemplate.opsForValue().get(refreshTokenKey); if (ObjectUtils.isEmpty(refreshToken)) { throw new CustomException(REFRESH_TOKEN_EXPIRED); } // 액세스 토큰 재발급 - String newAccessToken = tokenProvider.generateToken(email, TokenType.ACCESS); - tokenProvider.saveToken(newAccessToken, TokenType.ACCESS); + String newAccessToken = tokenProvider.generateToken(email, ACCESS); + tokenProvider.saveToken(newAccessToken, ACCESS); return new ReissueResponse(newAccessToken); } } diff --git a/src/main/java/com/example/solidconnection/config/token/TokenProvider.java b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java index 27e59656f..771a2ec89 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java @@ -40,7 +40,7 @@ public String generateToken(String email, TokenType tokenType) { public void saveToken(String token, TokenType tokenType) { redisTemplate.opsForValue().set( - tokenType.addTokenPrefixToSubject(getClaim(token).getSubject()), + tokenType.addPrefixToSubject(getClaim(token).getSubject()), token, tokenType.getExpireTime(), TimeUnit.MILLISECONDS diff --git a/src/main/java/com/example/solidconnection/config/token/TokenType.java b/src/main/java/com/example/solidconnection/config/token/TokenType.java index d5fc1717f..f056cd0dc 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenType.java +++ b/src/main/java/com/example/solidconnection/config/token/TokenType.java @@ -5,9 +5,9 @@ @Getter public enum TokenType { - ACCESS("", 1000 * 60 * 60), - REFRESH("refresh:", 1000 * 60 * 60 * 24 * 7), - KAKAO_OAUTH("kakao:", 1000 * 60 * 60); + ACCESS("", 1000 * 60 * 60), // 1hour + REFRESH("refresh:", 1000 * 60 * 60 * 24 * 7), // 7days + KAKAO_OAUTH("kakao:", 1000 * 60 * 60); // 1hour private final String prefix; private final int expireTime; @@ -17,7 +17,7 @@ public enum TokenType { this.expireTime = expireTime; } - public String addTokenPrefixToSubject(String subject) { + public String addPrefixToSubject(String subject) { return prefix + subject; } } diff --git a/src/main/java/com/example/solidconnection/config/token/TokenValidator.java b/src/main/java/com/example/solidconnection/config/token/TokenValidator.java index 9a63a21f5..ae56603b6 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenValidator.java +++ b/src/main/java/com/example/solidconnection/config/token/TokenValidator.java @@ -12,6 +12,8 @@ import java.util.Date; import java.util.Objects; +import static com.example.solidconnection.config.token.TokenType.*; +import static com.example.solidconnection.config.token.TokenType.REFRESH; import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_TOKEN_EXPIRED; import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN; import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; @@ -31,14 +33,14 @@ public class TokenValidator { public void validateAccessToken(String token) { validateTokenNotEmpty(token); - validateTokenNotExpired(token, TokenType.ACCESS); + validateTokenNotExpired(token, ACCESS); validateNotSignOut(token); validateRefreshToken(token); } public void validateKakaoToken(String token) { validateTokenNotEmpty(token); - validateTokenNotExpired(token, TokenType.KAKAO_OAUTH); + validateTokenNotExpired(token, KAKAO_OAUTH); validateKakaoTokenNotUsed(token); } @@ -52,10 +54,10 @@ private void validateTokenNotExpired(String token, TokenType tokenType) { Date expiration = getClaim(token).getExpiration(); long now = new Date().getTime(); if ((expiration.getTime() - now) < 0) { - if (tokenType.equals(TokenType.ACCESS)) { + if (tokenType.equals(ACCESS)) { throw new CustomException(ACCESS_TOKEN_EXPIRED); } - if (token.equals(TokenType.KAKAO_OAUTH)) { + if (token.equals(KAKAO_OAUTH)) { throw new CustomException(INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN); } } @@ -63,21 +65,21 @@ private void validateTokenNotExpired(String token, TokenType tokenType) { private void validateNotSignOut(String token) { String email = getClaim(token).getSubject(); - if (SIGN_OUT_VALUE.equals(redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email)))) { + if (SIGN_OUT_VALUE.equals(redisTemplate.opsForValue().get(REFRESH.addPrefixToSubject(email)))) { throw new CustomException(USER_ALREADY_SIGN_OUT); } } private void validateRefreshToken(String token) { String email = getClaim(token).getSubject(); - if (redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email)) == null) { + if (redisTemplate.opsForValue().get(REFRESH.addPrefixToSubject(email)) == null) { throw new CustomException(REFRESH_TOKEN_EXPIRED); } } private void validateKakaoTokenNotUsed(String token) { String email = getClaim(token).getSubject(); - if (!Objects.equals(redisTemplate.opsForValue().get(TokenType.KAKAO_OAUTH.addTokenPrefixToSubject(email)), token)) { + if (!Objects.equals(redisTemplate.opsForValue().get(KAKAO_OAUTH.addPrefixToSubject(email)), token)) { throw new CustomException(INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN); } } diff --git a/src/test/java/com/example/solidconnection/e2e/SignInTest.java b/src/test/java/com/example/solidconnection/e2e/SignInTest.java index 8f1bd1018..35eb3374f 100644 --- a/src/test/java/com/example/solidconnection/e2e/SignInTest.java +++ b/src/test/java/com/example/solidconnection/e2e/SignInTest.java @@ -5,7 +5,6 @@ import com.example.solidconnection.auth.dto.kakao.FirstAccessResponse; import com.example.solidconnection.auth.dto.kakao.KakaoCodeRequest; import com.example.solidconnection.auth.dto.kakao.KakaoUserInfoDto; -import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import io.restassured.RestAssured; @@ -19,6 +18,8 @@ import java.time.LocalDate; +import static com.example.solidconnection.config.token.TokenType.KAKAO_OAUTH; +import static com.example.solidconnection.config.token.TokenType.REFRESH; import static com.example.solidconnection.e2e.DynamicFixture.createKakaoUserInfoDtoByEmail; import static com.example.solidconnection.e2e.DynamicFixture.createSiteUserByEmail; import static com.example.solidconnection.scheduler.UserRemovalScheduler.ACCOUNT_RECOVER_DURATION; @@ -64,7 +65,7 @@ class SignInTest extends BaseEndToEndTest { () -> assertThat(response.nickname()).isEqualTo(kakaoProfileDto.nickname()), () -> assertThat(response.profileImageUrl()).isEqualTo(kakaoProfileDto.profileImageUrl()), () -> assertThat(response.kakaoOauthToken()).isNotNull()); - assertThat(redisTemplate.opsForValue().get(TokenType.KAKAO_OAUTH.addTokenPrefixToSubject(email))) + assertThat(redisTemplate.opsForValue().get(KAKAO_OAUTH.addPrefixToSubject(email))) .as("카카오 인증 토큰을 저장한다.") .isEqualTo(response.kakaoOauthToken()); } @@ -94,7 +95,7 @@ class SignInTest extends BaseEndToEndTest { () -> assertThat(response.isRegistered()).isTrue(), () -> assertThat(response.accessToken()).isNotNull(), () -> assertThat(response.refreshToken()).isNotNull()); - assertThat(redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email))) + assertThat(redisTemplate.opsForValue().get(REFRESH.addPrefixToSubject(email))) .as("리프레시 토큰을 저장한다.") .isEqualTo(response.refreshToken()); } @@ -128,7 +129,7 @@ class SignInTest extends BaseEndToEndTest { () -> assertThat(response.accessToken()).isNotNull(), () -> assertThat(response.refreshToken()).isNotNull(), () -> assertThat(siteUserRepository.getByEmail(email).getQuitedAt()).isNull()); - assertThat(redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email))) + assertThat(redisTemplate.opsForValue().get(REFRESH.addPrefixToSubject(email))) .as("리프레시 토큰을 저장한다.") .isEqualTo(response.refreshToken()); } diff --git a/src/test/java/com/example/solidconnection/e2e/SignUpTest.java b/src/test/java/com/example/solidconnection/e2e/SignUpTest.java index f4ca9e061..4222a5dff 100644 --- a/src/test/java/com/example/solidconnection/e2e/SignUpTest.java +++ b/src/test/java/com/example/solidconnection/e2e/SignUpTest.java @@ -3,7 +3,6 @@ import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.dto.SignUpResponse; import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.custom.response.ErrorResponse; import com.example.solidconnection.entity.Country; import com.example.solidconnection.entity.InterestedCountry; @@ -27,6 +26,8 @@ import java.util.List; +import static com.example.solidconnection.config.token.TokenType.KAKAO_OAUTH; +import static com.example.solidconnection.config.token.TokenType.REFRESH; import static com.example.solidconnection.custom.exception.ErrorCode.JWT_EXCEPTION; import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_EXISTED; @@ -69,8 +70,8 @@ class SignUpTest extends BaseEndToEndTest { // setup - 카카오 토큰 발급 String email = "email@email.com"; - String generatedKakaoToken = tokenProvider.generateToken(email, TokenType.KAKAO_OAUTH); - tokenProvider.saveToken(generatedKakaoToken, TokenType.KAKAO_OAUTH); + String generatedKakaoToken = tokenProvider.generateToken(email, KAKAO_OAUTH); + tokenProvider.saveToken(generatedKakaoToken, KAKAO_OAUTH); // request - body 생성 및 요청 List interestedRegionNames = List.of("유럽"); @@ -108,7 +109,7 @@ class SignUpTest extends BaseEndToEndTest { () -> assertThat(interestedCountries).containsExactlyInAnyOrderElementsOf(countries) ); - assertThat(redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email))) + assertThat(redisTemplate.opsForValue().get(REFRESH.addPrefixToSubject(email))) .as("리프레시 토큰을 저장한다.") .isEqualTo(response.refreshToken()); } @@ -122,8 +123,8 @@ class SignUpTest extends BaseEndToEndTest { // setup - 카카오 토큰 발급 String email = "email@email.com"; - String generatedKakaoToken = tokenProvider.generateToken(email, TokenType.KAKAO_OAUTH); - tokenProvider.saveToken(generatedKakaoToken, TokenType.KAKAO_OAUTH); + String generatedKakaoToken = tokenProvider.generateToken(email, KAKAO_OAUTH); + tokenProvider.saveToken(generatedKakaoToken, KAKAO_OAUTH); // request - body 생성 및 요청 SignUpRequest signUpRequest = new SignUpRequest(generatedKakaoToken, null, null, @@ -148,8 +149,8 @@ class SignUpTest extends BaseEndToEndTest { siteUserRepository.save(alreadyExistUser); // setup - 카카오 토큰 발급 - String generatedKakaoToken = tokenProvider.generateToken(alreadyExistEmail, TokenType.KAKAO_OAUTH); - tokenProvider.saveToken(generatedKakaoToken, TokenType.KAKAO_OAUTH); + String generatedKakaoToken = tokenProvider.generateToken(alreadyExistEmail, KAKAO_OAUTH); + tokenProvider.saveToken(generatedKakaoToken, KAKAO_OAUTH); // request - body 생성 및 요청 SignUpRequest signUpRequest = new SignUpRequest(generatedKakaoToken, null, null, From 7227179c729006fa11ef13731ab17cb2aad4b37d Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 14 Jan 2025 23:04:48 +0900 Subject: [PATCH 03/21] =?UTF-8?q?feat:=20subject=20=EC=B6=94=EC=B6=9C=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/token/TokenProvider.java | 44 ++++++++++++++++--- .../config/token/TokenValidator.java | 7 +-- .../custom/exception/ErrorCode.java | 3 +- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/example/solidconnection/config/token/TokenProvider.java b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java index 771a2ec89..2dc5c10d6 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java @@ -1,10 +1,12 @@ package com.example.solidconnection.config.token; +import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.custom.userdetails.CustomUserDetailsService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; @@ -16,10 +18,15 @@ import java.util.Date; import java.util.concurrent.TimeUnit; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; + @RequiredArgsConstructor @Component public class TokenProvider { + private static final String TOKEN_HEADER = "Authorization"; + private static final String TOKEN_PREFIX = "Bearer "; + private final RedisTemplate redisTemplate; private final CustomUserDetailsService customUserDetailsService; @@ -38,33 +45,56 @@ public String generateToken(String email, TokenType tokenType) { .compact(); } - public void saveToken(String token, TokenType tokenType) { + public String saveToken(String token, TokenType tokenType) { + String subject = parseSubjectOrElseThrow(token); redisTemplate.opsForValue().set( - tokenType.addPrefixToSubject(getClaim(token).getSubject()), + tokenType.addPrefixToSubject(subject), token, tokenType.getExpireTime(), TimeUnit.MILLISECONDS ); + return token; } public Authentication getAuthentication(String token) { - String email = getClaim(token).getSubject(); + String email = parseSubject(token); UserDetails userDetails = customUserDetailsService.loadUserByUsername(email); return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); } public String getEmail(String token) { - return getClaim(token).getSubject(); + return parseSubject(token); + } + + public String parseTokenFromRequest(HttpServletRequest request) { + String token = request.getHeader(TOKEN_HEADER); + if (token == null || token.isBlank() || !token.startsWith(TOKEN_PREFIX)) { + return null; + } + return token.substring(TOKEN_PREFIX.length()); + } + + public String parseSubject(String token) { + try { + return Jwts.parser() + .setSigningKey(secretKey) + .parseClaimsJws(token) + .getBody() + .getSubject(); + } catch (ExpiredJwtException e) { + return e.getClaims().getSubject(); + } } - private Claims getClaim(String token) { + public String parseSubjectOrElseThrow(String token) { try { return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) - .getBody(); + .getBody() + .getSubject(); } catch (ExpiredJwtException e) { - return e.getClaims(); + throw new CustomException(INVALID_TOKEN); } } } diff --git a/src/main/java/com/example/solidconnection/config/token/TokenValidator.java b/src/main/java/com/example/solidconnection/config/token/TokenValidator.java index ae56603b6..6f0708cdc 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenValidator.java +++ b/src/main/java/com/example/solidconnection/config/token/TokenValidator.java @@ -12,11 +12,12 @@ import java.util.Date; import java.util.Objects; -import static com.example.solidconnection.config.token.TokenType.*; +import static com.example.solidconnection.config.token.TokenType.ACCESS; +import static com.example.solidconnection.config.token.TokenType.KAKAO_OAUTH; import static com.example.solidconnection.config.token.TokenType.REFRESH; import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_TOKEN_EXPIRED; +import static com.example.solidconnection.custom.exception.ErrorCode.EMPTY_TOKEN; import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT; @@ -46,7 +47,7 @@ public void validateKakaoToken(String token) { private void validateTokenNotEmpty(String token) { if (!StringUtils.hasText(token)) { - throw new CustomException(INVALID_TOKEN); + throw new CustomException(EMPTY_TOKEN); } } diff --git a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java index 765013303..8c3032284 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java @@ -29,7 +29,8 @@ public enum ErrorCode { // auth USER_ALREADY_SIGN_OUT(HttpStatus.UNAUTHORIZED.value(), "로그아웃 되었습니다."), - INVALID_TOKEN(HttpStatus.UNAUTHORIZED.value(), "토큰이 필요한 경로에 빈 토큰으로 요청했습니다."), + EMPTY_TOKEN(HttpStatus.UNAUTHORIZED.value(), "토큰이 필요한 경로에 빈 토큰으로 요청했습니다."), + INVALID_TOKEN(HttpStatus.UNAUTHORIZED.value(), "유효하지 않은 토큰입니다."), AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED.value(), "인증이 필요한 접근입니다."), ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "액세스 토큰이 만료되었습니다. 재발급 api를 호출해주세요."), REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰이 만료되었습니다. 다시 로그인을 진행해주세요."), From e8632fc6afdbcf6c0745685840478e22fb5497f8 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 14 Jan 2025 23:05:26 +0900 Subject: [PATCH 04/21] =?UTF-8?q?test:=20TokenProvider=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존에 작성되지 않았던 것들도 작성함 --- .../config/token/TokenProviderTest.java | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java diff --git a/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java b/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java new file mode 100644 index 000000000..1a2b0884a --- /dev/null +++ b/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java @@ -0,0 +1,171 @@ +package com.example.solidconnection.config.token; + +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.custom.exception.ErrorCode; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +@TestContainerSpringBootTest +@DisplayName("TokenProvider 테스트") +class TokenProviderTest { + + @Autowired + private TokenProvider tokenProvider; + + @Autowired + private RedisTemplate redisTemplate; + + @Value("${jwt.secret}") + private String secretKey; + + private final String subject = "subject"; + + @Test + void 토큰을_생성한다() { + // when + String token = tokenProvider.generateToken(subject, TokenType.ACCESS); + + // then + String extractedSubject = Jwts.parser() + .setSigningKey(secretKey) + .parseClaimsJws(token) + .getBody() + .getSubject(); + assertThat(subject).isEqualTo(extractedSubject); + } + + @Nested + class 토큰을_저장한다 { + + @Test + void 토큰이_유효하면_저장한다() { + // given + String token = createValidToken(subject); + + // when + tokenProvider.saveToken(token, TokenType.ACCESS); + + // then + String savedToken = redisTemplate.opsForValue().get(TokenType.ACCESS.addPrefixToSubject(subject)); + assertThat(savedToken).isEqualTo(token); + } + + @Test + void 토큰이_유효하지않으면_예외가_발생한다() { + // given + String token = createInvalidToken(subject); + + // when & then + assertThatCode(() -> tokenProvider.saveToken(token, TokenType.REFRESH)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); + } + } + + @Nested + class 요청으로부터_토큰을_추출한다 { + + @Test + void 토큰이_있으면_토큰을_반환한다() { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + String token = "token"; + request.addHeader("Authorization", "Bearer " + token); + + // when + String extractedToken = tokenProvider.parseTokenFromRequest(request); + + // then + assertThat(extractedToken).isEqualTo(token); + } + + @Test + void 토큰이_없으면_null_을_반환한다() { + // given + MockHttpServletRequest noHeader = new MockHttpServletRequest(); + MockHttpServletRequest wrongPrefix = new MockHttpServletRequest(); + wrongPrefix.addHeader("Authorization", "Wrong token"); + MockHttpServletRequest emptyToken = new MockHttpServletRequest(); + wrongPrefix.addHeader("Authorization", "Bearer "); + + // when & then + assertAll( + () -> assertThat(tokenProvider.parseTokenFromRequest(noHeader)).isNull(), + () -> assertThat(tokenProvider.parseTokenFromRequest(wrongPrefix)).isNull(), + () -> assertThat(tokenProvider.parseTokenFromRequest(emptyToken)).isNull() + ); + } + } + + @Nested + class 토큰으로부터_subject_를_추출한다 { + + @Test + void 유효한_토큰의_subject_를_추출한다() { + // given + String token = createValidToken(subject); + + // when + String extractedSubject = tokenProvider.parseSubject(token); + + // then + assertThat(extractedSubject).isEqualTo(subject); + } + + @Test + void 유효하지_않은_토큰의_subject_를_추출한다() { + // given + String token = createInvalidToken(subject); + + // when + String extractedSubject = tokenProvider.parseSubject(token); + + // then + assertThat(extractedSubject).isEqualTo(subject); + } + + @Test + void 유효하지_않은_토큰의_subject_를_추출하면_예외가_발생한다() { + // given + String token = createInvalidToken(subject); + + // when + assertThatCode(() -> tokenProvider.parseSubjectOrElseThrow(token)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); + } + + } + + private String createValidToken(String subject) { + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000)) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + } + + private String createInvalidToken(String subject) { + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() - 1000)) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + } +} From feb828f4a79bc8b185cf6483c4c88456dd221dd2 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 14 Jan 2025 23:49:34 +0900 Subject: [PATCH 05/21] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=ED=95=A8=EC=88=98=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/JwtAuthenticationEntryPoint.java | 20 +++++-------------- .../security/JwtAuthenticationFilter.java | 4 ++-- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationEntryPoint.java b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationEntryPoint.java index 69f5a2f2d..31a3a7907 100644 --- a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationEntryPoint.java @@ -12,7 +12,6 @@ import java.io.IOException; -import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_TOKEN_EXPIRED; import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED; @Component @@ -25,24 +24,15 @@ public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { ErrorResponse errorResponse = new ErrorResponse(AUTHENTICATION_FAILED, authException.getMessage()); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); + writeResponse(response, errorResponse); } - public void expiredCommence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException { - ErrorResponse errorResponse = new ErrorResponse(new CustomException(ACCESS_TOKEN_EXPIRED)); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); + public void customCommence(HttpServletResponse response, CustomException customException) throws IOException { + ErrorResponse errorResponse = new ErrorResponse(customException); + writeResponse(response, errorResponse); } - public void customCommence(HttpServletRequest request, HttpServletResponse response, - CustomException customException) throws IOException { - ErrorResponse errorResponse = new ErrorResponse(customException); + private void writeResponse(HttpServletResponse response, ErrorResponse errorResponse) throws IOException { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); diff --git a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java index 44a252c2e..ca286eba2 100644 --- a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java @@ -68,14 +68,14 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } } catch (JwtExpiredTokenException e) { SecurityContextHolder.clearContext(); - jwtAuthenticationEntryPoint.expiredCommence(request, response, e); + jwtAuthenticationEntryPoint.commence(request, response, e); return; } catch (AuthenticationException e) { SecurityContextHolder.clearContext(); jwtAuthenticationEntryPoint.commence(request, response, e); return; } catch (CustomException e) { - jwtAuthenticationEntryPoint.customCommence(request, response, e); + jwtAuthenticationEntryPoint.customCommence(response, e); return; } filterChain.doFilter(request, response); // 다음 필터로 요청과 응답 전달 From b795eb88f1368b32acb9f18d0101cdc754faa4fa Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Wed, 15 Jan 2025 00:15:10 +0900 Subject: [PATCH 06/21] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=B2=B4=ED=81=AC=20=ED=95=84=ED=84=B0=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AS-IS: 액세스 토큰을 검증하면서 로그아웃했는지를 검증하고 있다. 이는 액세스 토큰의 검증부에 들어갈 것이 아니라, 더 이전 단계에서 처리되어야 한다. - TO-BE: 로그아웃 토큰을 필터에서 처리한다. 이전보다 더 빠르게 예외를 응답할 수 있다. --- .../auth/service/AuthService.java | 3 +- .../config/security/SignOutCheckFilter.java | 47 +++++++++++++++++++ .../config/token/TokenValidator.java | 11 ----- 3 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 06c392c10..a7372cf5e 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -17,13 +17,14 @@ import static com.example.solidconnection.config.token.TokenType.ACCESS; import static com.example.solidconnection.config.token.TokenType.REFRESH; -import static com.example.solidconnection.config.token.TokenValidator.SIGN_OUT_VALUE; import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; @RequiredArgsConstructor @Service public class AuthService { + public static final String SIGN_OUT_VALUE = "signOut"; + private final RedisTemplate redisTemplate; private final TokenProvider tokenProvider; private final SiteUserRepository siteUserRepository; diff --git a/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java new file mode 100644 index 000000000..eaf2c5539 --- /dev/null +++ b/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java @@ -0,0 +1,47 @@ +package com.example.solidconnection.config.security; + +import com.example.solidconnection.config.token.TokenProvider; +import com.example.solidconnection.custom.exception.CustomException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +import static com.example.solidconnection.auth.service.AuthService.SIGN_OUT_VALUE; +import static com.example.solidconnection.config.token.TokenType.REFRESH; +import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT; + +@Component +@RequiredArgsConstructor +public class SignOutCheckFilter extends OncePerRequestFilter { + + private final RedisTemplate redisTemplate; + private final TokenProvider tokenProvider; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws ServletException, IOException { + String token = tokenProvider.parseTokenFromRequest(request); + if (token == null || !isSignOut(token)) { + filterChain.doFilter(request, response); + return; + } + + jwtAuthenticationEntryPoint.customCommence(response, new CustomException(USER_ALREADY_SIGN_OUT)); + } + + private boolean isSignOut(String accessToken) { + String subject = tokenProvider.parseSubject(accessToken); + String refreshToken = REFRESH.addPrefixToSubject(subject); + return SIGN_OUT_VALUE.equals(redisTemplate.opsForValue().get(refreshToken)); + } +} diff --git a/src/main/java/com/example/solidconnection/config/token/TokenValidator.java b/src/main/java/com/example/solidconnection/config/token/TokenValidator.java index 6f0708cdc..bb9a55ce1 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenValidator.java +++ b/src/main/java/com/example/solidconnection/config/token/TokenValidator.java @@ -19,14 +19,11 @@ import static com.example.solidconnection.custom.exception.ErrorCode.EMPTY_TOKEN; import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN; import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; -import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT; @Component @RequiredArgsConstructor public class TokenValidator { - public static final String SIGN_OUT_VALUE = "signOut"; - private final RedisTemplate redisTemplate; @Value("${jwt.secret}") @@ -35,7 +32,6 @@ public class TokenValidator { public void validateAccessToken(String token) { validateTokenNotEmpty(token); validateTokenNotExpired(token, ACCESS); - validateNotSignOut(token); validateRefreshToken(token); } @@ -64,13 +60,6 @@ private void validateTokenNotExpired(String token, TokenType tokenType) { } } - private void validateNotSignOut(String token) { - String email = getClaim(token).getSubject(); - if (SIGN_OUT_VALUE.equals(redisTemplate.opsForValue().get(REFRESH.addPrefixToSubject(email)))) { - throw new CustomException(USER_ALREADY_SIGN_OUT); - } - } - private void validateRefreshToken(String token) { String email = getClaim(token).getSubject(); if (redisTemplate.opsForValue().get(REFRESH.addPrefixToSubject(email)) == null) { From 54141e1eb0dbb345e0a6a27d07e488b0e73e3a95 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Wed, 15 Jan 2025 00:15:59 +0900 Subject: [PATCH 07/21] =?UTF-8?q?test:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=B2=B4=ED=81=AC=20=ED=95=84=ED=84=B0=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/SignOutCheckFilterTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java diff --git a/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java new file mode 100644 index 000000000..3e125500f --- /dev/null +++ b/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java @@ -0,0 +1,107 @@ +package com.example.solidconnection.config.security; + +import com.example.solidconnection.support.TestContainerSpringBootTest; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +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.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.util.Date; +import java.util.Objects; + +import static com.example.solidconnection.config.token.TokenType.REFRESH; +import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.spy; + +@TestContainerSpringBootTest +@DisplayName("로그아웃 체크 필터 테스트") +class SignOutCheckFilterTest { + + @Autowired + private SignOutCheckFilter signOutCheckFilter; + + @Autowired + private RedisTemplate redisTemplate; + + @Value("${jwt.secret}") + private String secretKey; + + private HttpServletRequest request; + private HttpServletResponse response; + private FilterChain filterChain; + + private final String subject = "subject"; + + @BeforeEach + void setUp() { + response = new MockHttpServletResponse(); + filterChain = spy(FilterChain.class); + Objects.requireNonNull(redisTemplate.getConnectionFactory()) + .getConnection() + .serverCommands() + .flushDb(); + } + + @Test + void 로그아웃한_토큰이면_예외를_응답한다() throws Exception { + // given + request = createTokenRequest(subject); + String refreshTokenKey = REFRESH.addPrefixToSubject(subject); + redisTemplate.opsForValue().set(refreshTokenKey, "signOut"); + + // when + signOutCheckFilter.doFilterInternal(request, response, filterChain); + + // then + assertThat(response.getStatus()).isEqualTo(USER_ALREADY_SIGN_OUT.getCode()); + then(filterChain).shouldHaveNoMoreInteractions(); + } + + @Test + void 토큰이_없으면_다음_필터로_전달한다() throws Exception { + // given + request = new MockHttpServletRequest(); + + // when + signOutCheckFilter.doFilterInternal(request, response, filterChain); + + // then + then(filterChain).should().doFilter(request, response); + } + + @Test + void 로그아웃하지_않은_토큰이면_다음_필터로_전달한다() throws Exception { + // given + request = createTokenRequest(subject); + + // when + signOutCheckFilter.doFilterInternal(request, response, filterChain); + + // then + then(filterChain).should().doFilter(request, response); + } + + private HttpServletRequest createTokenRequest(String subject) { + String token = Jwts.builder() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000)) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + token); + return request; + } +} From e89dc769958ea5c03cfc241a1a08649d898a78f0 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Wed, 15 Jan 2025 02:49:13 +0900 Subject: [PATCH 08/21] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=ED=95=A8=EC=88=98=EB=A1=9C=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/token/TokenProvider.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/example/solidconnection/config/token/TokenProvider.java b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java index 2dc5c10d6..da3a7dcb7 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java @@ -76,11 +76,7 @@ public String parseTokenFromRequest(HttpServletRequest request) { public String parseSubject(String token) { try { - return Jwts.parser() - .setSigningKey(secretKey) - .parseClaimsJws(token) - .getBody() - .getSubject(); + return extractSubject(token); } catch (ExpiredJwtException e) { return e.getClaims().getSubject(); } @@ -88,13 +84,17 @@ public String parseSubject(String token) { public String parseSubjectOrElseThrow(String token) { try { - return Jwts.parser() - .setSigningKey(secretKey) - .parseClaimsJws(token) - .getBody() - .getSubject(); + return extractSubject(token); } catch (ExpiredJwtException e) { throw new CustomException(INVALID_TOKEN); } } + + private String extractSubject(String token) throws ExpiredJwtException { + return Jwts.parser() + .setSigningKey(secretKey) + .parseClaimsJws(token) + .getBody() + .getSubject(); + } } From ff902d2a7dd54cff742714fc299d17ccd2893cc1 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Wed, 15 Jan 2025 03:21:42 +0900 Subject: [PATCH 09/21] =?UTF-8?q?refactor:=20JWT=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 가독성 개선 - 다른 객체들에 책임 분산 - permitAllEndpoint에 대한 설정 제거 --- .../config/security/JwtAuthentication.java | 29 ++++++ .../security/JwtAuthenticationEntryPoint.java | 5 + .../security/JwtAuthenticationFilter.java | 95 ++++--------------- .../security/JwtUserDetails.java} | 17 ++-- 4 files changed, 60 insertions(+), 86 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/config/security/JwtAuthentication.java rename src/main/java/com/example/solidconnection/{custom/userdetails/CustomUserDetails.java => config/security/JwtUserDetails.java} (60%) diff --git a/src/main/java/com/example/solidconnection/config/security/JwtAuthentication.java b/src/main/java/com/example/solidconnection/config/security/JwtAuthentication.java new file mode 100644 index 000000000..84692709a --- /dev/null +++ b/src/main/java/com/example/solidconnection/config/security/JwtAuthentication.java @@ -0,0 +1,29 @@ +package com.example.solidconnection.config.security; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +public class JwtAuthentication extends AbstractAuthenticationToken { + + private final String token; + private final Object principal; + + public JwtAuthentication(Object principal, String token, Collection authorities) { + super(authorities); + this.token = token; + this.principal = principal; + setAuthenticated(true); + } + + @Override + public Object getCredentials() { + return this.token; + } + + @Override + public Object getPrincipal() { + return this.principal; + } +} diff --git a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationEntryPoint.java b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationEntryPoint.java index 31a3a7907..7487858be 100644 --- a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationEntryPoint.java @@ -27,6 +27,11 @@ public void commence(HttpServletRequest request, HttpServletResponse response, writeResponse(response, errorResponse); } + public void generalCommence(HttpServletResponse response, Exception exception) throws IOException { + ErrorResponse errorResponse = new ErrorResponse(AUTHENTICATION_FAILED, exception.getMessage()); + writeResponse(response, errorResponse); + } + public void customCommence(HttpServletResponse response, CustomException customException) throws IOException { ErrorResponse errorResponse = new ErrorResponse(customException); writeResponse(response, errorResponse); diff --git a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java index ca286eba2..439a743b5 100644 --- a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java @@ -1,113 +1,58 @@ package com.example.solidconnection.config.security; import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenValidator; import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.exception.JwtExpiredTokenException; -import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; -import org.springframework.util.AntPathMatcher; -import org.springframework.util.ObjectUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; -import java.util.HashSet; - -import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_TOKEN_EXPIRED; @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - public static final String TOKEN_HEADER = "Authorization"; - public static final String TOKEN_PREFIX = "Bearer "; + private static final String REISSUE_URI = "/auth/reissue"; + private static final String REISSUE_METHOD = "post"; private final TokenProvider tokenProvider; - private final TokenValidator tokenValidator; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - // 인증 정보를 저장할 필요 없는 url - AntPathMatcher pathMatcher = new AntPathMatcher(); - for (String endpoint : getPermitAllEndpoints()) { - if (pathMatcher.match(endpoint, request.getRequestURI())) { - filterChain.doFilter(request, response); - return; - } + protected void doFilterInternal(@NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) throws ServletException, IOException { + String token = tokenProvider.parseTokenFromRequest(request); + if (token == null || isReissueRequest(request)) { + filterChain.doFilter(request, response); + return; } - // 토큰 검증 try { - String token = this.resolveAccessTokenFromRequest(request); // 웹 요청에서 토큰 추출 - if (token != null) { // 토큰이 있어야 검증 - 토큰 유무에 대한 다른 처리를 컨트롤러에서 할 수 있음 - try { - String requestURI = request.getRequestURI(); - if (requestURI.equals("/auth/reissue")) { - Authentication auth = this.tokenProvider.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(auth); - filterChain.doFilter(request, response); - return; - } - tokenValidator.validateAccessToken(token); // 액세스 토큰 검증 - 비어있는지, 유효한지, 리프레시 토큰, 로그아웃 - } catch (ExpiredJwtException e) { - throw new JwtExpiredTokenException(ACCESS_TOKEN_EXPIRED.getMessage()); - } - Authentication auth = this.tokenProvider.getAuthentication(token); // 토큰에서 인증 정보 가져옴 - SecurityContextHolder.getContext().setAuthentication(auth);// 인증 정보를 보안 컨텍스트에 설정 - } - } catch (JwtExpiredTokenException e) { - SecurityContextHolder.clearContext(); - jwtAuthenticationEntryPoint.commence(request, response, e); - return; + String subject = tokenProvider.parseSubjectOrElseThrow(token); + UserDetails userDetails = new JwtUserDetails(subject); + Authentication auth = new JwtAuthentication(userDetails, token, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(auth); + filterChain.doFilter(request, response); } catch (AuthenticationException e) { - SecurityContextHolder.clearContext(); jwtAuthenticationEntryPoint.commence(request, response, e); - return; } catch (CustomException e) { jwtAuthenticationEntryPoint.customCommence(response, e); - return; - } - filterChain.doFilter(request, response); // 다음 필터로 요청과 응답 전달 - } - - private String resolveAccessTokenFromRequest(HttpServletRequest request) { - String token = request.getHeader(TOKEN_HEADER); - - if (!ObjectUtils.isEmpty(token) && token.startsWith(TOKEN_PREFIX)) { // 토큰이 비어 있지 않고, Bearer로 시작한다면 - return token.substring(TOKEN_PREFIX.length()); // Bearer 제외한 실제 토큰 부분 반환 + } catch (Exception e) { + jwtAuthenticationEntryPoint.generalCommence(response, e); } - return null; } - private HashSet getPermitAllEndpoints() { - var permitAllEndpoints = new HashSet(); - - // 서버 정상 작동 확인 - permitAllEndpoints.add("/"); - permitAllEndpoints.add("/index.html"); - permitAllEndpoints.add("/favicon.ico"); - - // 이미지 업로드 - permitAllEndpoints.add("/file/profile/pre"); - - // 토큰이 필요하지 않은 인증 - permitAllEndpoints.add("/auth/kakao"); - permitAllEndpoints.add("/auth/sign-up"); - - // 대학교 정보 - permitAllEndpoints.add("/university/search/**"); - - return permitAllEndpoints; + private boolean isReissueRequest(HttpServletRequest request) { + return REISSUE_URI.equals(request.getRequestURI()) && REISSUE_METHOD.equals(request.getMethod()); } } diff --git a/src/main/java/com/example/solidconnection/custom/userdetails/CustomUserDetails.java b/src/main/java/com/example/solidconnection/config/security/JwtUserDetails.java similarity index 60% rename from src/main/java/com/example/solidconnection/custom/userdetails/CustomUserDetails.java rename to src/main/java/com/example/solidconnection/config/security/JwtUserDetails.java index 5d992adaf..b3bbda5fa 100644 --- a/src/main/java/com/example/solidconnection/custom/userdetails/CustomUserDetails.java +++ b/src/main/java/com/example/solidconnection/config/security/JwtUserDetails.java @@ -1,26 +1,21 @@ -package com.example.solidconnection.custom.userdetails; +package com.example.solidconnection.config.security; -import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; -public class CustomUserDetails implements UserDetails {//todo: principal 을 썼을 때 바로 SiteUser를 반환하게 하면 안되나?? +public class JwtUserDetails implements UserDetails { - private final SiteUser siteUser; + private final String userName; - public CustomUserDetails(SiteUser siteUser) { - this.siteUser = siteUser; - } - - public String getEmail() { - return siteUser.getEmail(); + public JwtUserDetails(String userName) { + this.userName = userName; } @Override public String getUsername() { - return siteUser.getEmail(); + return this.userName; } @Override From d0e092997c03a1a4d30ced9671926560e67c395c Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Wed, 15 Jan 2025 03:22:16 +0900 Subject: [PATCH 10/21] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C,=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/token/TokenProvider.java | 11 -------- .../userdetails/CustomUserDetailsService.java | 25 ------------------- 2 files changed, 36 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/custom/userdetails/CustomUserDetailsService.java diff --git a/src/main/java/com/example/solidconnection/config/token/TokenProvider.java b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java index da3a7dcb7..adbbd3a99 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java @@ -1,7 +1,6 @@ package com.example.solidconnection.config.token; import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.userdetails.CustomUserDetailsService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; @@ -10,9 +9,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.Date; @@ -28,7 +24,6 @@ public class TokenProvider { private static final String TOKEN_PREFIX = "Bearer "; private final RedisTemplate redisTemplate; - private final CustomUserDetailsService customUserDetailsService; @Value("${jwt.secret}") private String secretKey; @@ -56,12 +51,6 @@ public String saveToken(String token, TokenType tokenType) { return token; } - public Authentication getAuthentication(String token) { - String email = parseSubject(token); - UserDetails userDetails = customUserDetailsService.loadUserByUsername(email); - return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); - } - public String getEmail(String token) { return parseSubject(token); } diff --git a/src/main/java/com/example/solidconnection/custom/userdetails/CustomUserDetailsService.java b/src/main/java/com/example/solidconnection/custom/userdetails/CustomUserDetailsService.java deleted file mode 100644 index c9f1b1606..000000000 --- a/src/main/java/com/example/solidconnection/custom/userdetails/CustomUserDetailsService.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.solidconnection.custom.userdetails; - -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.stereotype.Service; - -import static com.example.solidconnection.custom.exception.ErrorCode.USER_NOT_FOUND; - -@Service -@RequiredArgsConstructor -public class CustomUserDetailsService implements UserDetailsService { - - private final SiteUserRepository siteUserRepository; - - @Override - public UserDetails loadUserByUsername(String username) { - SiteUser siteUser = siteUserRepository.findByEmail(username) - .orElseThrow(() -> new CustomException(USER_NOT_FOUND, username)); - return new CustomUserDetails(siteUser); - } -} From 53299c90915f90065f54abef5acfb8002fc5bfb4 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Wed, 15 Jan 2025 03:23:09 +0900 Subject: [PATCH 11/21] =?UTF-8?q?test:=20JWT=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/JwtAuthenticationFilterTest.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/config/security/JwtAuthenticationFilterTest.java diff --git a/src/test/java/com/example/solidconnection/config/security/JwtAuthenticationFilterTest.java b/src/test/java/com/example/solidconnection/config/security/JwtAuthenticationFilterTest.java new file mode 100644 index 000000000..3fed1fb90 --- /dev/null +++ b/src/test/java/com/example/solidconnection/config/security/JwtAuthenticationFilterTest.java @@ -0,0 +1,127 @@ +package com.example.solidconnection.config.security; + +import com.example.solidconnection.support.TestContainerSpringBootTest; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.spy; + +@TestContainerSpringBootTest +@DisplayName("토큰 인증 필터 테스트") +class JwtAuthenticationFilterTest { + + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @Value("${jwt.secret}") + private String secretKey; + + private HttpServletRequest request; + private HttpServletResponse response; + private FilterChain filterChain; + + @BeforeEach + void setUp() { + response = new MockHttpServletResponse(); + filterChain = spy(FilterChain.class); + SecurityContextHolder.clearContext(); + } + + @Test + public void 유효한_토큰에_대한_인증_정보를_저장한다() throws Exception { + // given + String token = Jwts.builder() + .setSubject("subject") + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000)) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + request = createRequestWithToken(token); + + // when + jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); + + // then + assertThat(SecurityContextHolder.getContext().getAuthentication()).isExactlyInstanceOf(JwtAuthentication.class); + then(filterChain).should().doFilter(request, response); + } + + @Test + public void 토큰이_없으면_다음_필터로_진행한다() throws Exception { + // given + request = new MockHttpServletRequest(); + + // when + jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); + + // then + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + then(filterChain).should().doFilter(request, response); + } + + @Nested + class 유효하지_않은_토큰으로_인증하면_예외를_응답한다 { + + @Test + public void 만료된_토큰으로_인증하면_예외를_응답한다() throws Exception { + // given + String token = Jwts.builder() + .setSubject("subject") + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() - 1000)) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + request = createRequestWithToken(token); + + // when + jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); + + // then + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + then(filterChain).shouldHaveNoMoreInteractions(); + } + + @Test + public void 서명하지_않은_토큰으로_인증하면_예외를_응답한다() throws Exception { + // given + String token = Jwts.builder() + .setSubject("subject") + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() - 1000)) + .signWith(SignatureAlgorithm.HS256, "wrongSecretKey") + .compact(); + request = createRequestWithToken(token); + + // when + jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); + + // then + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + then(filterChain).shouldHaveNoMoreInteractions(); + } + } + + private HttpServletRequest createRequestWithToken(String token) { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + token); + return request; + } +} From c33466b40c636d9b8b2d82c6aae1079c7a86c9a1 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Wed, 15 Jan 2025 04:35:42 +0900 Subject: [PATCH 12/21] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=94=84=EB=A7=81?= =?UTF-8?q?=20=EC=8B=9C=ED=81=90=EB=A6=AC=ED=8B=B0=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 가독성 향상 - 필터 추가 - 시큐리티 단에서 관리하는 인증 필요없는 uri 제거 --- .../security/SecurityConfiguration.java | 42 +++++++------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java index 70bcf6c37..ac7517e00 100644 --- a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java @@ -4,62 +4,50 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import java.util.Arrays; - @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) @RequiredArgsConstructor public class SecurityConfiguration { - private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CorsPropertiesConfig corsProperties; + private final SignOutCheckFilter signOutCheckFilter; + private final JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(corsProperties.getAllowedOrigins()); - configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); - configuration.setAllowedHeaders(Arrays.asList("*")); + configuration.addAllowedMethod("*"); + configuration.addAllowedHeader("*"); configuration.setAllowCredentials(true); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); + return source; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource())) + return http .httpBasic(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) - .sessionManagement((session) -> session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(authorizeRequest - -> authorizeRequest - .requestMatchers( - "/", "/index.html", "/favicon.ico", - "/file/profile/pre", - "/auth/kakao", "/auth/sign-up", "/auth/reissue", - "/university/detail/**", "/university/search/**", "/university/recommends", - "/actuator/**" - ) - .permitAll() - .anyRequest().authenticated()) - .addFilterBefore(this.jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .formLogin(AbstractHttpConfigurer::disable); - - return http.build(); + .formLogin(AbstractHttpConfigurer::disable) + .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource())) + .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) + .addFilterBefore(this.jwtAuthenticationFilter, BasicAuthenticationFilter.class) + .addFilterBefore(this.signOutCheckFilter, JwtAuthenticationFilter.class) + .build(); } } From aaca84fbadb11ae354a31ae2968237f1b831d17c Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 21 Jan 2025 03:41:15 +0900 Subject: [PATCH 13/21] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=EB=90=9C=20cors=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/cors/CorsPropertiesConfig.java | 17 -------------- .../config/cors/WebConfig.java | 22 ------------------- 2 files changed, 39 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/config/cors/CorsPropertiesConfig.java delete mode 100644 src/main/java/com/example/solidconnection/config/cors/WebConfig.java diff --git a/src/main/java/com/example/solidconnection/config/cors/CorsPropertiesConfig.java b/src/main/java/com/example/solidconnection/config/cors/CorsPropertiesConfig.java deleted file mode 100644 index 68144d733..000000000 --- a/src/main/java/com/example/solidconnection/config/cors/CorsPropertiesConfig.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.solidconnection.config.cors; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -import java.util.List; - -@Getter -@Setter -@ConfigurationProperties(prefix = "cors") -@Configuration -public class CorsPropertiesConfig { - - private List allowedOrigins; -} diff --git a/src/main/java/com/example/solidconnection/config/cors/WebConfig.java b/src/main/java/com/example/solidconnection/config/cors/WebConfig.java deleted file mode 100644 index 00f3cf411..000000000 --- a/src/main/java/com/example/solidconnection/config/cors/WebConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.solidconnection.config.cors; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -@RequiredArgsConstructor -public class WebConfig implements WebMvcConfigurer { - - private final CorsPropertiesConfig corsProperties; - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins(corsProperties.getAllowedOrigins().toArray(new String[0])) - .allowedMethods("*") - .allowedHeaders("*") - .allowCredentials(true); - } -} From 1d37ff40f8b7af1134f5acbdffc23db0283ef873 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 21 Jan 2025 03:42:52 +0900 Subject: [PATCH 14/21] =?UTF-8?q?refactor:=20cors=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20ConfigurationProperties=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/config/security/CorsProperties.java | 9 +++++++++ .../config/security/SecurityConfiguration.java | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/config/security/CorsProperties.java diff --git a/src/main/java/com/example/solidconnection/config/security/CorsProperties.java b/src/main/java/com/example/solidconnection/config/security/CorsProperties.java new file mode 100644 index 000000000..f851692c6 --- /dev/null +++ b/src/main/java/com/example/solidconnection/config/security/CorsProperties.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.config.security; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +@ConfigurationProperties(prefix = "cors") +public record CorsProperties(List allowedOrigins) { +} diff --git a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java index ac7517e00..eb6ffec76 100644 --- a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java @@ -1,7 +1,7 @@ package com.example.solidconnection.config.security; -import com.example.solidconnection.config.cors.CorsPropertiesConfig; import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -17,16 +17,17 @@ @Configuration @EnableWebSecurity @RequiredArgsConstructor +@EnableConfigurationProperties({CorsProperties.class, JwtProperties.class}) public class SecurityConfiguration { - private final CorsPropertiesConfig corsProperties; + private final CorsProperties corsProperties; private final SignOutCheckFilter signOutCheckFilter; private final JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(corsProperties.getAllowedOrigins()); + configuration.setAllowedOrigins(corsProperties.allowedOrigins()); configuration.addAllowedMethod("*"); configuration.addAllowedHeader("*"); configuration.setAllowCredentials(true); From b60798828602746c2b3477392912bf7c72051ecf Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 21 Jan 2025 03:46:27 +0900 Subject: [PATCH 15/21] =?UTF-8?q?refactor:=20TokenType=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{config/token => auth/domain}/TokenType.java | 8 ++++---- .../example/solidconnection/auth/service/AuthService.java | 4 ++-- .../solidconnection/auth/service/SignInService.java | 3 +-- .../solidconnection/auth/service/SignUpService.java | 4 +--- .../{config/token => auth/service}/TokenValidator.java | 7 ++++--- .../config/security/SignOutCheckFilter.java | 1 + .../config/security/SignOutCheckFilterTest.java | 2 +- .../solidconnection/config/token/TokenProviderTest.java | 3 +++ .../example/solidconnection/e2e/ApplicantsQueryTest.java | 1 + .../java/com/example/solidconnection/e2e/MyPageTest.java | 1 + .../com/example/solidconnection/e2e/MyPageUpdateTest.java | 1 + .../java/com/example/solidconnection/e2e/SignInTest.java | 4 ++-- .../example/solidconnection/e2e/UniversityDetailTest.java | 2 +- .../example/solidconnection/e2e/UniversityLikeTest.java | 2 +- .../solidconnection/e2e/UniversityRecommendTest.java | 2 +- .../example/solidconnection/e2e/UniversitySearchTest.java | 2 +- 16 files changed, 26 insertions(+), 21 deletions(-) rename src/main/java/com/example/solidconnection/{config/token => auth/domain}/TokenType.java (62%) rename src/main/java/com/example/solidconnection/{config/token => auth/service}/TokenValidator.java (91%) diff --git a/src/main/java/com/example/solidconnection/config/token/TokenType.java b/src/main/java/com/example/solidconnection/auth/domain/TokenType.java similarity index 62% rename from src/main/java/com/example/solidconnection/config/token/TokenType.java rename to src/main/java/com/example/solidconnection/auth/domain/TokenType.java index f056cd0dc..7fa6045f7 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenType.java +++ b/src/main/java/com/example/solidconnection/auth/domain/TokenType.java @@ -1,13 +1,13 @@ -package com.example.solidconnection.config.token; +package com.example.solidconnection.auth.domain; import lombok.Getter; @Getter public enum TokenType { - ACCESS("", 1000 * 60 * 60), // 1hour - REFRESH("refresh:", 1000 * 60 * 60 * 24 * 7), // 7days - KAKAO_OAUTH("kakao:", 1000 * 60 * 60); // 1hour + ACCESS("ACCESS:", 1000 * 60 * 60), // 1hour + REFRESH("REFRESH:", 1000 * 60 * 60 * 24 * 7), // 7days + KAKAO_OAUTH("KAKAO:", 1000 * 60 * 60); // 1hour private final String prefix; private final int expireTime; diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index a7372cf5e..d714d12a3 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -15,8 +15,8 @@ import java.time.LocalDate; import java.util.concurrent.TimeUnit; -import static com.example.solidconnection.config.token.TokenType.ACCESS; -import static com.example.solidconnection.config.token.TokenType.REFRESH; +import static com.example.solidconnection.auth.domain.TokenType.ACCESS; +import static com.example.solidconnection.auth.domain.TokenType.REFRESH; import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/auth/service/SignInService.java b/src/main/java/com/example/solidconnection/auth/service/SignInService.java index acfcfe8f5..2cd356d73 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignInService.java @@ -6,8 +6,7 @@ import com.example.solidconnection.auth.dto.kakao.KakaoCodeRequest; import com.example.solidconnection.auth.dto.kakao.KakaoOauthResponse; import com.example.solidconnection.auth.dto.kakao.KakaoUserInfoDto; -import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java index ebae98a41..5cbd781eb 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java @@ -2,9 +2,7 @@ import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.dto.SignUpResponse; -import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; -import com.example.solidconnection.config.token.TokenValidator; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.entity.InterestedCountry; import com.example.solidconnection.entity.InterestedRegion; diff --git a/src/main/java/com/example/solidconnection/config/token/TokenValidator.java b/src/main/java/com/example/solidconnection/auth/service/TokenValidator.java similarity index 91% rename from src/main/java/com/example/solidconnection/config/token/TokenValidator.java rename to src/main/java/com/example/solidconnection/auth/service/TokenValidator.java index bb9a55ce1..6d172ef3f 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenValidator.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenValidator.java @@ -1,5 +1,6 @@ package com.example.solidconnection.config.token; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.custom.exception.CustomException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -12,9 +13,9 @@ import java.util.Date; import java.util.Objects; -import static com.example.solidconnection.config.token.TokenType.ACCESS; -import static com.example.solidconnection.config.token.TokenType.KAKAO_OAUTH; -import static com.example.solidconnection.config.token.TokenType.REFRESH; +import static com.example.solidconnection.auth.domain.TokenType.ACCESS; +import static com.example.solidconnection.auth.domain.TokenType.KAKAO_OAUTH; +import static com.example.solidconnection.auth.domain.TokenType.REFRESH; import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_TOKEN_EXPIRED; import static com.example.solidconnection.custom.exception.ErrorCode.EMPTY_TOKEN; import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN; diff --git a/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java index eaf2c5539..f7ee79cd1 100644 --- a/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java @@ -14,6 +14,7 @@ import java.io.IOException; +import static com.example.solidconnection.auth.domain.TokenType.REFRESH; import static com.example.solidconnection.auth.service.AuthService.SIGN_OUT_VALUE; import static com.example.solidconnection.config.token.TokenType.REFRESH; import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT; diff --git a/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java index 3e125500f..860309dbe 100644 --- a/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java +++ b/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java @@ -18,7 +18,7 @@ import java.util.Date; import java.util.Objects; -import static com.example.solidconnection.config.token.TokenType.REFRESH; +import static com.example.solidconnection.auth.domain.TokenType.REFRESH; import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.then; diff --git a/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java b/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java index 1a2b0884a..66143dd9e 100644 --- a/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.config.token; +import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.config.security.JwtProperties; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.custom.exception.ErrorCode; import com.example.solidconnection.support.TestContainerSpringBootTest; diff --git a/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java b/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java index 8caf6c204..6d684b8ba 100644 --- a/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java +++ b/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java @@ -9,6 +9,7 @@ import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.type.VerifyStatus; diff --git a/src/test/java/com/example/solidconnection/e2e/MyPageTest.java b/src/test/java/com/example/solidconnection/e2e/MyPageTest.java index 66b4d5b7d..81db8be16 100644 --- a/src/test/java/com/example/solidconnection/e2e/MyPageTest.java +++ b/src/test/java/com/example/solidconnection/e2e/MyPageTest.java @@ -2,6 +2,7 @@ import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.repository.SiteUserRepository; diff --git a/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java b/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java index 7be4ba4b9..8aaa58064 100644 --- a/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java +++ b/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java @@ -2,6 +2,7 @@ import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.custom.response.ErrorResponse; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageUpdateResponse; diff --git a/src/test/java/com/example/solidconnection/e2e/SignInTest.java b/src/test/java/com/example/solidconnection/e2e/SignInTest.java index 35eb3374f..efd5ad1d7 100644 --- a/src/test/java/com/example/solidconnection/e2e/SignInTest.java +++ b/src/test/java/com/example/solidconnection/e2e/SignInTest.java @@ -18,8 +18,8 @@ import java.time.LocalDate; -import static com.example.solidconnection.config.token.TokenType.KAKAO_OAUTH; -import static com.example.solidconnection.config.token.TokenType.REFRESH; +import static com.example.solidconnection.auth.domain.TokenType.KAKAO_OAUTH; +import static com.example.solidconnection.auth.domain.TokenType.REFRESH; import static com.example.solidconnection.e2e.DynamicFixture.createKakaoUserInfoDtoByEmail; import static com.example.solidconnection.e2e.DynamicFixture.createSiteUserByEmail; import static com.example.solidconnection.scheduler.UserRemovalScheduler.ACCOUNT_RECOVER_DURATION; diff --git a/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java b/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java index d4dbb533b..92d8e8d99 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.e2e; import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.university.dto.LanguageRequirementResponse; diff --git a/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java b/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java index ef6467d47..2820196d8 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.e2e; import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; diff --git a/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java b/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java index 32899829a..7b676bc56 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.e2e; import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.entity.InterestedCountry; import com.example.solidconnection.entity.InterestedRegion; import com.example.solidconnection.repositories.InterestedCountyRepository; diff --git a/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java b/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java index c6ddd37be..c9a8c4c74 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.e2e; import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; From 995ab181ad7708ec0b3e8f0f45c54f85be998f62 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 21 Jan 2025 03:48:47 +0900 Subject: [PATCH 16/21] =?UTF-8?q?refactor:=20TokenProvider,=20TokenValidat?= =?UTF-8?q?or=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/AuthService.java | 1 - .../auth/service/TokenProvider.java | 51 +++++++++++ .../auth/service/TokenValidator.java | 2 +- .../security/JwtAuthenticationFilter.java | 1 - .../config/security/SignOutCheckFilter.java | 1 - .../config/token/TokenProvider.java | 89 ------------------- .../e2e/ApplicantsQueryTest.java | 3 +- .../solidconnection/e2e/MyPageTest.java | 3 +- .../solidconnection/e2e/MyPageUpdateTest.java | 3 +- .../solidconnection/e2e/SignUpTest.java | 6 +- .../e2e/UniversityDetailTest.java | 2 +- .../e2e/UniversityLikeTest.java | 2 +- .../e2e/UniversityRecommendTest.java | 2 +- .../e2e/UniversitySearchTest.java | 2 +- 14 files changed, 62 insertions(+), 106 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/auth/service/TokenProvider.java delete mode 100644 src/main/java/com/example/solidconnection/config/token/TokenProvider.java diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index d714d12a3..29fb1b347 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -2,7 +2,6 @@ import com.example.solidconnection.auth.dto.ReissueResponse; -import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java new file mode 100644 index 000000000..693a968ea --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java @@ -0,0 +1,51 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.config.security.JwtProperties; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import static com.example.solidconnection.util.JwtUtils.parseSubject; +import static com.example.solidconnection.util.JwtUtils.parseSubjectOrElseThrow; + +@RequiredArgsConstructor +@Component +public class TokenProvider { + + private final RedisTemplate redisTemplate; + private final JwtProperties jwtProperties; + + public String generateToken(String email, TokenType tokenType) { + Claims claims = Jwts.claims().setSubject(email); + Date now = new Date(); + Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime()); + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(expiredDate) + .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) + .compact(); + } + + public String saveToken(String token, TokenType tokenType) { + String subject = parseSubjectOrElseThrow(token, jwtProperties.secret()); + redisTemplate.opsForValue().set( + tokenType.addPrefixToSubject(subject), + token, + tokenType.getExpireTime(), + TimeUnit.MILLISECONDS + ); + return token; + } + + public String getEmail(String token) { + return parseSubject(token, jwtProperties.secret()); + } +} diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenValidator.java b/src/main/java/com/example/solidconnection/auth/service/TokenValidator.java index 6d172ef3f..8c17ad00c 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenValidator.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenValidator.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.config.token; +package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.custom.exception.CustomException; diff --git a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java index 439a743b5..14e599144 100644 --- a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java @@ -1,6 +1,5 @@ package com.example.solidconnection.config.security; -import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.custom.exception.CustomException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; diff --git a/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java index f7ee79cd1..182fee816 100644 --- a/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java @@ -1,6 +1,5 @@ package com.example.solidconnection.config.security; -import com.example.solidconnection.config.token.TokenProvider; import com.example.solidconnection.custom.exception.CustomException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; diff --git a/src/main/java/com/example/solidconnection/config/token/TokenProvider.java b/src/main/java/com/example/solidconnection/config/token/TokenProvider.java deleted file mode 100644 index adbbd3a99..000000000 --- a/src/main/java/com/example/solidconnection/config/token/TokenProvider.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.example.solidconnection.config.token; - -import com.example.solidconnection.custom.exception.CustomException; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Component; - -import java.util.Date; -import java.util.concurrent.TimeUnit; - -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; - -@RequiredArgsConstructor -@Component -public class TokenProvider { - - private static final String TOKEN_HEADER = "Authorization"; - private static final String TOKEN_PREFIX = "Bearer "; - - private final RedisTemplate redisTemplate; - - @Value("${jwt.secret}") - private String secretKey; - - public String generateToken(String email, TokenType tokenType) { - Claims claims = Jwts.claims().setSubject(email); - Date now = new Date(); - Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime()); - return Jwts.builder() - .setClaims(claims) - .setIssuedAt(now) - .setExpiration(expiredDate) - .signWith(SignatureAlgorithm.HS512, this.secretKey) - .compact(); - } - - public String saveToken(String token, TokenType tokenType) { - String subject = parseSubjectOrElseThrow(token); - redisTemplate.opsForValue().set( - tokenType.addPrefixToSubject(subject), - token, - tokenType.getExpireTime(), - TimeUnit.MILLISECONDS - ); - return token; - } - - public String getEmail(String token) { - return parseSubject(token); - } - - public String parseTokenFromRequest(HttpServletRequest request) { - String token = request.getHeader(TOKEN_HEADER); - if (token == null || token.isBlank() || !token.startsWith(TOKEN_PREFIX)) { - return null; - } - return token.substring(TOKEN_PREFIX.length()); - } - - public String parseSubject(String token) { - try { - return extractSubject(token); - } catch (ExpiredJwtException e) { - return e.getClaims().getSubject(); - } - } - - public String parseSubjectOrElseThrow(String token) { - try { - return extractSubject(token); - } catch (ExpiredJwtException e) { - throw new CustomException(INVALID_TOKEN); - } - } - - private String extractSubject(String token) throws ExpiredJwtException { - return Jwts.parser() - .setSigningKey(secretKey) - .parseClaimsJws(token) - .getBody() - .getSubject(); - } -} diff --git a/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java b/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java index 6d684b8ba..6b739248b 100644 --- a/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java +++ b/src/test/java/com/example/solidconnection/e2e/ApplicantsQueryTest.java @@ -7,8 +7,7 @@ import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.UniversityApplicantsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; diff --git a/src/test/java/com/example/solidconnection/e2e/MyPageTest.java b/src/test/java/com/example/solidconnection/e2e/MyPageTest.java index 81db8be16..fb42216c9 100644 --- a/src/test/java/com/example/solidconnection/e2e/MyPageTest.java +++ b/src/test/java/com/example/solidconnection/e2e/MyPageTest.java @@ -1,7 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; diff --git a/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java b/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java index 8aaa58064..6d7f52032 100644 --- a/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java +++ b/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java @@ -1,7 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenProvider; -import com.example.solidconnection.config.token.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.custom.response.ErrorResponse; import com.example.solidconnection.siteuser.domain.SiteUser; diff --git a/src/test/java/com/example/solidconnection/e2e/SignUpTest.java b/src/test/java/com/example/solidconnection/e2e/SignUpTest.java index 4222a5dff..07dafb539 100644 --- a/src/test/java/com/example/solidconnection/e2e/SignUpTest.java +++ b/src/test/java/com/example/solidconnection/e2e/SignUpTest.java @@ -2,7 +2,7 @@ import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.dto.SignUpResponse; -import com.example.solidconnection.config.token.TokenProvider; +import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.custom.response.ErrorResponse; import com.example.solidconnection.entity.Country; import com.example.solidconnection.entity.InterestedCountry; @@ -26,8 +26,8 @@ import java.util.List; -import static com.example.solidconnection.config.token.TokenType.KAKAO_OAUTH; -import static com.example.solidconnection.config.token.TokenType.REFRESH; +import static com.example.solidconnection.auth.domain.TokenType.KAKAO_OAUTH; +import static com.example.solidconnection.auth.domain.TokenType.REFRESH; import static com.example.solidconnection.custom.exception.ErrorCode.JWT_EXCEPTION; import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_EXISTED; diff --git a/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java b/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java index 92d8e8d99..947f44fd0 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversityDetailTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenProvider; +import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; diff --git a/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java b/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java index 2820196d8..06bd175dc 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversityLikeTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenProvider; +import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; diff --git a/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java b/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java index 7b676bc56..00afbc8e3 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversityRecommendTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenProvider; +import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.entity.InterestedCountry; import com.example.solidconnection.entity.InterestedRegion; diff --git a/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java b/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java index c9a8c4c74..4859f9fe2 100644 --- a/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java +++ b/src/test/java/com/example/solidconnection/e2e/UniversitySearchTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.e2e; -import com.example.solidconnection.config.token.TokenProvider; +import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; From 505b746a7ecfa165adffdcb69c337f20d6e57336 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 21 Jan 2025 03:51:01 +0900 Subject: [PATCH 17/21] =?UTF-8?q?refactor:=20JwtProperties=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/JwtAuthenticationFilter.java | 4 +- .../config/security/JwtProperties.java | 7 ++ .../config/security/SignOutCheckFilter.java | 3 +- .../security/JwtAuthenticationFilterTest.java | 12 +-- .../security/SignOutCheckFilterTest.java | 5 +- .../config/token/TokenProviderTest.java | 98 ++----------------- 6 files changed, 28 insertions(+), 101 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/config/security/JwtProperties.java diff --git a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java index 14e599144..8afb5eb7f 100644 --- a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java @@ -23,7 +23,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private static final String REISSUE_URI = "/auth/reissue"; private static final String REISSUE_METHOD = "post"; - private final TokenProvider tokenProvider; + private final JwtProperties jwtProperties; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Override @@ -37,7 +37,7 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, } try { - String subject = tokenProvider.parseSubjectOrElseThrow(token); + String subject = parseSubjectOrElseThrow(token, jwtProperties.secret()); UserDetails userDetails = new JwtUserDetails(subject); Authentication auth = new JwtAuthentication(userDetails, token, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); diff --git a/src/main/java/com/example/solidconnection/config/security/JwtProperties.java b/src/main/java/com/example/solidconnection/config/security/JwtProperties.java new file mode 100644 index 000000000..e0c63da46 --- /dev/null +++ b/src/main/java/com/example/solidconnection/config/security/JwtProperties.java @@ -0,0 +1,7 @@ +package com.example.solidconnection.config.security; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "jwt") +public record JwtProperties(String secret) { +} diff --git a/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java index 182fee816..d4f20fb85 100644 --- a/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java @@ -15,7 +15,6 @@ import static com.example.solidconnection.auth.domain.TokenType.REFRESH; import static com.example.solidconnection.auth.service.AuthService.SIGN_OUT_VALUE; -import static com.example.solidconnection.config.token.TokenType.REFRESH; import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT; @Component @@ -23,7 +22,7 @@ public class SignOutCheckFilter extends OncePerRequestFilter { private final RedisTemplate redisTemplate; - private final TokenProvider tokenProvider; + private final JwtProperties jwtProperties; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Override diff --git a/src/test/java/com/example/solidconnection/config/security/JwtAuthenticationFilterTest.java b/src/test/java/com/example/solidconnection/config/security/JwtAuthenticationFilterTest.java index 3fed1fb90..c0256f75a 100644 --- a/src/test/java/com/example/solidconnection/config/security/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/example/solidconnection/config/security/JwtAuthenticationFilterTest.java @@ -11,7 +11,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.core.context.SecurityContextHolder; @@ -29,8 +28,8 @@ class JwtAuthenticationFilterTest { @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; - @Value("${jwt.secret}") - private String secretKey; + @Autowired + private JwtProperties jwtProperties; private HttpServletRequest request; private HttpServletResponse response; @@ -50,7 +49,7 @@ void setUp() { .setSubject("subject") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 1000)) - .signWith(SignatureAlgorithm.HS256, secretKey) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) .compact(); request = createRequestWithToken(token); @@ -58,7 +57,8 @@ void setUp() { jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); // then - assertThat(SecurityContextHolder.getContext().getAuthentication()).isExactlyInstanceOf(JwtAuthentication.class); + assertThat(SecurityContextHolder.getContext().getAuthentication()) + .isExactlyInstanceOf(JwtAuthentication.class); then(filterChain).should().doFilter(request, response); } @@ -85,7 +85,7 @@ class 유효하지_않은_토큰으로_인증하면_예외를_응답한다 { .setSubject("subject") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() - 1000)) - .signWith(SignatureAlgorithm.HS256, secretKey) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) .compact(); request = createRequestWithToken(token); diff --git a/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java index 860309dbe..47dfa6ee7 100644 --- a/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java +++ b/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java @@ -10,7 +10,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -34,8 +33,8 @@ class SignOutCheckFilterTest { @Autowired private RedisTemplate redisTemplate; - @Value("${jwt.secret}") - private String secretKey; + @Autowired + private JwtProperties jwtProperties; private HttpServletRequest request; private HttpServletResponse response; diff --git a/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java b/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java index 66143dd9e..d3992a33a 100644 --- a/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/config/token/TokenProviderTest.java @@ -12,15 +12,12 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.mock.web.MockHttpServletRequest; import java.util.Date; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; @TestContainerSpringBootTest @DisplayName("TokenProvider 테스트") @@ -32,19 +29,18 @@ class TokenProviderTest { @Autowired private RedisTemplate redisTemplate; - @Value("${jwt.secret}") - private String secretKey; - - private final String subject = "subject"; + @Autowired + private JwtProperties jwtProperties; @Test void 토큰을_생성한다() { // when + String subject = "subject123"; String token = tokenProvider.generateToken(subject, TokenType.ACCESS); // then String extractedSubject = Jwts.parser() - .setSigningKey(secretKey) + .setSigningKey(jwtProperties.secret()) .parseClaimsJws(token) .getBody() .getSubject(); @@ -57,6 +53,7 @@ class 토큰을_저장한다 { @Test void 토큰이_유효하면_저장한다() { // given + String subject = "subject321"; String token = createValidToken(subject); // when @@ -70,7 +67,7 @@ class 토큰을_저장한다 { @Test void 토큰이_유효하지않으면_예외가_발생한다() { // given - String token = createInvalidToken(subject); + String token = createInvalidToken(); // when & then assertThatCode(() -> tokenProvider.saveToken(token, TokenType.REFRESH)) @@ -79,96 +76,21 @@ class 토큰을_저장한다 { } } - @Nested - class 요청으로부터_토큰을_추출한다 { - - @Test - void 토큰이_있으면_토큰을_반환한다() { - // given - MockHttpServletRequest request = new MockHttpServletRequest(); - String token = "token"; - request.addHeader("Authorization", "Bearer " + token); - - // when - String extractedToken = tokenProvider.parseTokenFromRequest(request); - - // then - assertThat(extractedToken).isEqualTo(token); - } - - @Test - void 토큰이_없으면_null_을_반환한다() { - // given - MockHttpServletRequest noHeader = new MockHttpServletRequest(); - MockHttpServletRequest wrongPrefix = new MockHttpServletRequest(); - wrongPrefix.addHeader("Authorization", "Wrong token"); - MockHttpServletRequest emptyToken = new MockHttpServletRequest(); - wrongPrefix.addHeader("Authorization", "Bearer "); - - // when & then - assertAll( - () -> assertThat(tokenProvider.parseTokenFromRequest(noHeader)).isNull(), - () -> assertThat(tokenProvider.parseTokenFromRequest(wrongPrefix)).isNull(), - () -> assertThat(tokenProvider.parseTokenFromRequest(emptyToken)).isNull() - ); - } - } - - @Nested - class 토큰으로부터_subject_를_추출한다 { - - @Test - void 유효한_토큰의_subject_를_추출한다() { - // given - String token = createValidToken(subject); - - // when - String extractedSubject = tokenProvider.parseSubject(token); - - // then - assertThat(extractedSubject).isEqualTo(subject); - } - - @Test - void 유효하지_않은_토큰의_subject_를_추출한다() { - // given - String token = createInvalidToken(subject); - - // when - String extractedSubject = tokenProvider.parseSubject(token); - - // then - assertThat(extractedSubject).isEqualTo(subject); - } - - @Test - void 유효하지_않은_토큰의_subject_를_추출하면_예외가_발생한다() { - // given - String token = createInvalidToken(subject); - - // when - assertThatCode(() -> tokenProvider.parseSubjectOrElseThrow(token)) - .isInstanceOf(CustomException.class) - .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); - } - - } - private String createValidToken(String subject) { return Jwts.builder() .setSubject(subject) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 1000)) - .signWith(SignatureAlgorithm.HS256, secretKey) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) .compact(); } - private String createInvalidToken(String subject) { + private String createInvalidToken() { return Jwts.builder() - .setSubject(subject) + .setSubject("subject") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() - 1000)) - .signWith(SignatureAlgorithm.HS256, secretKey) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) .compact(); } } From a4b0db46c208632529cdf17cb573cfbbe29715f2 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 21 Jan 2025 03:51:14 +0900 Subject: [PATCH 18/21] =?UTF-8?q?refactor:=20JwtUtils=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/JwtAuthenticationFilter.java | 5 +- .../config/security/SignOutCheckFilter.java | 6 +- .../solidconnection/util/JwtUtils.java | 48 +++++++ .../security/SignOutCheckFilterTest.java | 2 +- .../solidconnection/util/JwtUtilsTest.java | 120 ++++++++++++++++++ 5 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/util/JwtUtils.java create mode 100644 src/test/java/com/example/solidconnection/util/JwtUtilsTest.java diff --git a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java index 8afb5eb7f..e01009be1 100644 --- a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java @@ -16,6 +16,9 @@ import java.io.IOException; +import static com.example.solidconnection.util.JwtUtils.parseSubjectOrElseThrow; +import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; + @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -30,7 +33,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - String token = tokenProvider.parseTokenFromRequest(request); + String token = parseTokenFromRequest(request); if (token == null || isReissueRequest(request)) { filterChain.doFilter(request, response); return; diff --git a/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java index d4f20fb85..3c1218d13 100644 --- a/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/SignOutCheckFilter.java @@ -16,6 +16,8 @@ import static com.example.solidconnection.auth.domain.TokenType.REFRESH; import static com.example.solidconnection.auth.service.AuthService.SIGN_OUT_VALUE; import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT; +import static com.example.solidconnection.util.JwtUtils.parseSubject; +import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; @Component @RequiredArgsConstructor @@ -29,7 +31,7 @@ public class SignOutCheckFilter extends OncePerRequestFilter { protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - String token = tokenProvider.parseTokenFromRequest(request); + String token = parseTokenFromRequest(request); if (token == null || !isSignOut(token)) { filterChain.doFilter(request, response); return; @@ -39,7 +41,7 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, } private boolean isSignOut(String accessToken) { - String subject = tokenProvider.parseSubject(accessToken); + String subject = parseSubject(accessToken, jwtProperties.secret()); String refreshToken = REFRESH.addPrefixToSubject(subject); return SIGN_OUT_VALUE.equals(redisTemplate.opsForValue().get(refreshToken)); } diff --git a/src/main/java/com/example/solidconnection/util/JwtUtils.java b/src/main/java/com/example/solidconnection/util/JwtUtils.java new file mode 100644 index 000000000..e42eb9a12 --- /dev/null +++ b/src/main/java/com/example/solidconnection/util/JwtUtils.java @@ -0,0 +1,48 @@ +package com.example.solidconnection.util; + +import com.example.solidconnection.custom.exception.CustomException; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; + +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; + +@Component +public class JwtUtils { + + private static final String TOKEN_HEADER = "Authorization"; + private static final String TOKEN_PREFIX = "Bearer "; + + public static String parseTokenFromRequest(HttpServletRequest request) { + String token = request.getHeader(TOKEN_HEADER); + if (token == null || token.isBlank() || !token.startsWith(TOKEN_PREFIX)) { + return null; + } + return token.substring(TOKEN_PREFIX.length()); + } + + public static String parseSubject(String token, String secretKey) { + try { + return extractSubject(token, secretKey); + } catch (ExpiredJwtException e) { + return e.getClaims().getSubject(); + } + } + + public static String parseSubjectOrElseThrow(String token, String secretKey) { + try { + return extractSubject(token, secretKey); + } catch (ExpiredJwtException e) { + throw new CustomException(INVALID_TOKEN); + } + } + + private static String extractSubject(String token, String secretKey) throws ExpiredJwtException { + return Jwts.parser() + .setSigningKey(secretKey) + .parseClaimsJws(token) + .getBody() + .getSubject(); + } +} diff --git a/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java index 47dfa6ee7..13544152b 100644 --- a/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java +++ b/src/test/java/com/example/solidconnection/config/security/SignOutCheckFilterTest.java @@ -96,7 +96,7 @@ private HttpServletRequest createTokenRequest(String subject) { .setSubject(subject) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 1000)) - .signWith(SignatureAlgorithm.HS256, secretKey) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) .compact(); MockHttpServletRequest request = new MockHttpServletRequest(); diff --git a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java b/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java new file mode 100644 index 000000000..69926a6db --- /dev/null +++ b/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java @@ -0,0 +1,120 @@ +package com.example.solidconnection.util; + +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.custom.exception.ErrorCode; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Date; + +import static com.example.solidconnection.util.JwtUtils.parseSubject; +import static com.example.solidconnection.util.JwtUtils.parseSubjectOrElseThrow; +import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("JwtUtils 테스트") +class JwtUtilsTest { + + private final String jwtSecretKey = "jwt-secret-key"; + + @Nested + class 요청으로부터_토큰을_추출한다 { + + @Test + void 토큰이_있으면_토큰을_반환한다() { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + String token = "token"; + request.addHeader("Authorization", "Bearer " + token); + + // when + String extractedToken = parseTokenFromRequest(request); + + // then + assertThat(extractedToken).isEqualTo(token); + } + + @Test + void 토큰이_없으면_null_을_반환한다() { + // given + MockHttpServletRequest noHeader = new MockHttpServletRequest(); + MockHttpServletRequest wrongPrefix = new MockHttpServletRequest(); + wrongPrefix.addHeader("Authorization", "Wrong token"); + MockHttpServletRequest emptyToken = new MockHttpServletRequest(); + wrongPrefix.addHeader("Authorization", "Bearer "); + + // when & then + assertAll( + () -> assertThat(parseTokenFromRequest(noHeader)).isNull(), + () -> assertThat(parseTokenFromRequest(wrongPrefix)).isNull(), + () -> assertThat(parseTokenFromRequest(emptyToken)).isNull() + ); + } + } + + @Nested + class 토큰으로부터_subject_를_추출한다 { + + @Test + void 유효한_토큰의_subject_를_추출한다() { + // given + String subject = "subject000"; + String token = createValidToken(subject); + + // when + String extractedSubject = parseSubject(token, jwtSecretKey); + + // then + assertThat(extractedSubject).isEqualTo(subject); + } + + @Test + void 유효하지_않은_토큰의_subject_를_추출한다() { + // given + String subject = "subject999"; + String token = createInvalidToken(subject); + + // when + String extractedSubject = parseSubject(token, jwtSecretKey); + + // then + assertThat(extractedSubject).isEqualTo(subject); + } + + @Test + void 유효하지_않은_토큰의_subject_를_추출하면_예외가_발생한다() { + // given + String subject = "subject123"; + String token = createInvalidToken(subject); + + // when + assertThatCode(() -> parseSubjectOrElseThrow(token, jwtSecretKey)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); + } + } + + private String createValidToken(String subject) { + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000)) + .signWith(SignatureAlgorithm.HS256, jwtSecretKey) + .compact(); + } + + private String createInvalidToken(String subject) { + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() - 1000)) + .signWith(SignatureAlgorithm.HS256, jwtSecretKey) + .compact(); + } +} From 201937088112f010557131972bf40ee001757e4e Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 21 Jan 2025 08:08:24 +0900 Subject: [PATCH 19/21] =?UTF-8?q?refactor:=20ConfigurationPropertiesScan?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/solidconnection/SolidConnectionApplication.java | 2 ++ .../solidconnection/config/security/SecurityConfiguration.java | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/solidconnection/SolidConnectionApplication.java b/src/main/java/com/example/solidconnection/SolidConnectionApplication.java index 670a3f0f7..a7f0554a3 100644 --- a/src/main/java/com/example/solidconnection/SolidConnectionApplication.java +++ b/src/main/java/com/example/solidconnection/SolidConnectionApplication.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.cache.annotation.EnableCaching; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.scheduling.annotation.EnableScheduling; +@ConfigurationPropertiesScan @EnableScheduling @EnableJpaAuditing @EnableCaching diff --git a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java index eb6ffec76..d28d883ca 100644 --- a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java @@ -1,7 +1,6 @@ package com.example.solidconnection.config.security; import lombok.RequiredArgsConstructor; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -17,7 +16,6 @@ @Configuration @EnableWebSecurity @RequiredArgsConstructor -@EnableConfigurationProperties({CorsProperties.class, JwtProperties.class}) public class SecurityConfiguration { private final CorsProperties corsProperties; From d9c965be6fc47964e929995e5e2d4a492d144538 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 23 Jan 2025 02:07:23 +0900 Subject: [PATCH 20/21] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4=ED=99=94=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/solidconnection/util/JwtUtils.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/example/solidconnection/util/JwtUtils.java b/src/main/java/com/example/solidconnection/util/JwtUtils.java index e42eb9a12..a3775365d 100644 --- a/src/main/java/com/example/solidconnection/util/JwtUtils.java +++ b/src/main/java/com/example/solidconnection/util/JwtUtils.java @@ -14,6 +14,9 @@ public class JwtUtils { private static final String TOKEN_HEADER = "Authorization"; private static final String TOKEN_PREFIX = "Bearer "; + private JwtUtils() { + } + public static String parseTokenFromRequest(HttpServletRequest request) { String token = request.getHeader(TOKEN_HEADER); if (token == null || token.isBlank() || !token.startsWith(TOKEN_PREFIX)) { From 605f24aee234d6055e27050c8881fca6ae2764be Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 23 Jan 2025 02:10:32 +0900 Subject: [PATCH 21/21] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B4=EB=A6=84=EC=97=90=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/solidconnection/util/JwtUtilsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java b/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java index 69926a6db..4dfc11540 100644 --- a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java +++ b/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java @@ -88,7 +88,7 @@ class 토큰으로부터_subject_를_추출한다 { } @Test - void 유효하지_않은_토큰의_subject_를_추출하면_예외가_발생한다() { + void 유효하지_않은_토큰의_subject_를_추출하면_예외_응답을_반환한다() { // given String subject = "subject123"; String token = createInvalidToken(subject);