From b3ac2982acd6963596f1ab24f37a37ffb265707b Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Tue, 22 Aug 2023 20:07:43 +0900 Subject: [PATCH 1/4] =?UTF-8?q?`AuthHelper`=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/hcommerce/heecommerce/auth/AuthHelper.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/hcommerce/heecommerce/auth/AuthHelper.java diff --git a/src/main/java/com/hcommerce/heecommerce/auth/AuthHelper.java b/src/main/java/com/hcommerce/heecommerce/auth/AuthHelper.java new file mode 100644 index 0000000..967693b --- /dev/null +++ b/src/main/java/com/hcommerce/heecommerce/auth/AuthHelper.java @@ -0,0 +1,11 @@ +package com.hcommerce.heecommerce.auth; + +import com.hcommerce.heecommerce.user.UserQueryRepository; +import jakarta.servlet.http.HttpServletRequest; + +public interface AuthHelper { + + boolean isAuthenticatedUser(HttpServletRequest request, UserQueryRepository userQueryRepository); + + AuthUserInfo getAuthUserInfo(String authInfo); +} From 968f13c6b840a186a7dca346e61188c639356639 Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Tue, 22 Aug 2023 20:07:58 +0900 Subject: [PATCH 2/4] =?UTF-8?q?`JwtAuthHelper`=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../heecommerce/auth/AuthConfig.java | 21 ++ .../heecommerce/auth/JwtAuthHelper.java | 113 +++++++++++ .../heecommerce/auth/JwtAuthHelperTest.java | 181 ++++++++++++++++++ 3 files changed, 315 insertions(+) create mode 100644 src/main/java/com/hcommerce/heecommerce/auth/AuthConfig.java create mode 100644 src/main/java/com/hcommerce/heecommerce/auth/JwtAuthHelper.java create mode 100644 src/test/java/com/hcommerce/heecommerce/auth/JwtAuthHelperTest.java diff --git a/src/main/java/com/hcommerce/heecommerce/auth/AuthConfig.java b/src/main/java/com/hcommerce/heecommerce/auth/AuthConfig.java new file mode 100644 index 0000000..89fab75 --- /dev/null +++ b/src/main/java/com/hcommerce/heecommerce/auth/AuthConfig.java @@ -0,0 +1,21 @@ +package com.hcommerce.heecommerce.auth; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AuthConfig { + + private final JwtAuthHelper jwtAuthHelper; + + @Autowired + public AuthConfig(JwtAuthHelper jwtAuthHelper) { + this.jwtAuthHelper = jwtAuthHelper; + } + + @Bean + public AuthHelper authHelper() { + return jwtAuthHelper; // TODO : 추후에 세션으로 관리하는 걸로 바뀌면 이곳 수정해줘야 함. + } +} diff --git a/src/main/java/com/hcommerce/heecommerce/auth/JwtAuthHelper.java b/src/main/java/com/hcommerce/heecommerce/auth/JwtAuthHelper.java new file mode 100644 index 0000000..e8c1535 --- /dev/null +++ b/src/main/java/com/hcommerce/heecommerce/auth/JwtAuthHelper.java @@ -0,0 +1,113 @@ +package com.hcommerce.heecommerce.auth; + +import com.hcommerce.heecommerce.common.utils.JwtUtils; +import com.hcommerce.heecommerce.user.UserQueryRepository; +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class JwtAuthHelper implements AuthHelper { + + private final String AUTH_TYPE = "Bearer"; + + private final JwtUtils jwtUtils; + + @Autowired + public JwtAuthHelper(JwtUtils jwtUtils) { + this.jwtUtils = jwtUtils; + } + + + /** + * isAuthenticatedUser는 HTTP 요청이 인증된 사용자에 의한 것인지를 판단하는 함수이다. + */ + @Override + public boolean isAuthenticatedUser(HttpServletRequest request, UserQueryRepository userQueryRepository) { + String authorization = request.getHeader("Authorization"); + + if(authorization == null || authorization.isBlank()) { + return false; + } + + if(!isValidAuthType(authorization)) { + return false; + } + + String accessToken = extractAccessToken(authorization); + + if(accessToken == null || accessToken.isBlank()) { + return false; + } + + AuthUserInfo authUserInfo = parseAccessToken(accessToken); + + if(authUserInfo == null) { + return false; + } + + boolean hasUserId = userQueryRepository.hasUserId(authUserInfo.getUserId()); + + if(!hasUserId) { + return false; + } + + return true; + } + + /** + * getAuthUserInfo 는 HTTP Header 의 authorization 를 피상해서 accessToken에 담긴 정보를 리턴하는 함수이다. + * 각 단계별로 유효성 검사를 할 수 있겠지만, 다른 기능 구현에 집중하기 위해 시간 관계상 + * AuthInterceptor 에서 authorization 의 유효성이 모두 유효하게 판단된 상황을 가정해서 따로 추가하지 않았다. + */ + @Override + public AuthUserInfo getAuthUserInfo(String auth) { + String accessToken = extractAccessToken(auth); + + AuthUserInfo authUserInfo = parseAccessToken(accessToken); + + return authUserInfo; + } + + /** + * isValidAuthType 는 HTTP Header 의 authorization 의 인증 유형이 유효한 인증 유형인지를 판단하는 함수이다. + */ + private boolean isValidAuthType(String authorization) { + return authorization.startsWith(AUTH_TYPE); + } + + /** + * isValidAuthType 는 HTTP Header 의 authorization로부터 accessToken을 추춣하는 함수이다. + */ + private String extractAccessToken(String authorization) { + String[] authorizationUnit = authorization.split(AUTH_TYPE+" "); + + if(authorizationUnit.length < 2) { + return null; + } + + String accessToken = authorizationUnit[1]; + + if(accessToken.isBlank()) { + return null; + } + + return accessToken; + } + + /** + * parseAccessToken 는 accessToken에 파싱하여 저장된 사용자 인증 정보를 리턴하는 함수이다. + */ + private AuthUserInfo parseAccessToken(String accessToken) { + Claims claims = jwtUtils.decode(accessToken); + + if(claims == null) { + return null; + } + + int userId = claims.get("userId", Integer.class); + + return new AuthUserInfo(userId); + } +} diff --git a/src/test/java/com/hcommerce/heecommerce/auth/JwtAuthHelperTest.java b/src/test/java/com/hcommerce/heecommerce/auth/JwtAuthHelperTest.java new file mode 100644 index 0000000..e795628 --- /dev/null +++ b/src/test/java/com/hcommerce/heecommerce/auth/JwtAuthHelperTest.java @@ -0,0 +1,181 @@ +package com.hcommerce.heecommerce.auth; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.BDDMockito.given; + +import com.hcommerce.heecommerce.common.utils.JwtUtils; +import com.hcommerce.heecommerce.fixture.AuthFixture; +import com.hcommerce.heecommerce.fixture.JwtFixture; +import com.hcommerce.heecommerce.fixture.UserFixture; +import com.hcommerce.heecommerce.user.UserQueryRepository; +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.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mock.web.MockHttpServletRequest; + +@DisplayName("JwtAuthHelper") +class JwtAuthHelperTest { + + @Mock + private JwtUtils jwtUtils; + + @Value("${jwt.secret}") + private String secret; + + @InjectMocks + private JwtAuthHelper jwtAuthHelper; + + @Mock + private UserQueryRepository userQueryRepository; + + private MockHttpServletRequest request; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + request = new MockHttpServletRequest(); + } + + @Nested + @DisplayName("isAuthenticatedUser") + class Describe_IsAuthenticatedUser { + @Nested + @DisplayName("with valid authorization") + class Context_With_Valid_Authorization { + @Test + @DisplayName("returns true") + void It_returns_true() { + // given + request.addHeader("Authorization", AuthFixture.AUTHORIZATION); + + given(jwtUtils.decode(any())).willReturn(JwtFixture.CLAIMS); + + given(userQueryRepository.hasUserId(anyInt())).willReturn(true); + + // when + boolean result = jwtAuthHelper.isAuthenticatedUser(request, userQueryRepository); + + // then + assertTrue(result); + } + } + + @Nested + @DisplayName("when authorization is empty") + class Context_When_Authorization_Is_Empty { + @Test + @DisplayName("returns false") + void It_returns_false() { + // given + request.addHeader("Authorization", ""); + + given(jwtUtils.decode(any())).willReturn(JwtFixture.CLAIMS); + + given(userQueryRepository.hasUserId(anyInt())).willReturn(true); + + // when + boolean result = jwtAuthHelper.isAuthenticatedUser(request, userQueryRepository); + + // then + assertFalse(result); + } + } + + @Nested + @DisplayName("with invalid authType") + class Context_With_Invalid_AuthType { + @Test + @DisplayName("returns false") + void It_returns_false() { + // given + request.addHeader("Authorization", AuthFixture.AUTHORIZATION_WITH_INVALID_AUTH_TYPE); + + // when + boolean result = jwtAuthHelper.isAuthenticatedUser(request, userQueryRepository); + + // then + assertFalse(result); + } + } + + // accessToken + @Nested + @DisplayName("with invalid accessToken") + class Context_With_Invalid_AccessToken { + @Test + @DisplayName("returns false") + void It_returns_false() { + // given + request.addHeader("Authorization", AuthFixture.AUTHORIZATION_WITHOUT_TOKEN); + + // when + boolean result = jwtAuthHelper.isAuthenticatedUser(request, userQueryRepository); + + // then + assertFalse(result); + } + } + + @Nested + @DisplayName("with invalid authUserInfo") + class Context_With_Invalid_AuthUserInfo { + @Test + @DisplayName("returns false") + void It_returns_false() { + // given + request.addHeader("Authorization", AuthFixture.AUTHORIZATION_WITHOUT_TOKEN_PAYLOAD); + + given(jwtUtils.decode(any())).willReturn(null); + + // when + boolean result = jwtAuthHelper.isAuthenticatedUser(request, userQueryRepository); + + // then + assertFalse(result); + } + } + // authUserInfo + + @Nested + @DisplayName("with invalid userId") + class Context_With_Invalid_UserId { + @Test + @DisplayName("returns true") + void It_returns_true() { + // given + request.addHeader("Authorization", AuthFixture.AUTHORIZATION); + + given(jwtUtils.decode(any())).willReturn(JwtFixture.CLAIMS); + + given(userQueryRepository.hasUserId(anyInt())).willReturn(false); + + // when + boolean result = jwtAuthHelper.isAuthenticatedUser(request, userQueryRepository); + + // then + assertFalse(result); + } + } + } + + @Nested + @DisplayName("getAuthUserInfo") + class Describe_GetAuthUserInfo { + @Test + @DisplayName("returns tokenPayload with `userId`") + void It_returns_TokenPayload() { + given(jwtUtils.decode(any())).willReturn(JwtFixture.CLAIMS); + + AuthUserInfo authUserInfo = jwtAuthHelper.getAuthUserInfo(AuthFixture.AUTHORIZATION); + + assertEquals(authUserInfo.getUserId(), UserFixture.ID); + } + } +} From 17e9c125b55c540cde28b75056a6d2857866344b Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Tue, 22 Aug 2023 20:10:49 +0900 Subject: [PATCH 3/4] =?UTF-8?q?`AuthHelper`=20=EC=B6=94=EA=B0=80=EC=97=90?= =?UTF-8?q?=20=EB=94=B0=EB=A5=B8=20`AuthenticationService`=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - parseAuthorization -> getAuthUserInfo 네이밍 수정한 이유는 좀더 유연한 네이밍으로 수정 --- .../auth/AuthenticationService.java | 110 ++++-------------- .../auth/AuthenticationServiceTest.java | 105 ++--------------- 2 files changed, 32 insertions(+), 183 deletions(-) diff --git a/src/main/java/com/hcommerce/heecommerce/auth/AuthenticationService.java b/src/main/java/com/hcommerce/heecommerce/auth/AuthenticationService.java index 0772aab..a6c57bc 100644 --- a/src/main/java/com/hcommerce/heecommerce/auth/AuthenticationService.java +++ b/src/main/java/com/hcommerce/heecommerce/auth/AuthenticationService.java @@ -1,65 +1,42 @@ package com.hcommerce.heecommerce.auth; -import com.hcommerce.heecommerce.common.utils.JwtUtils; import com.hcommerce.heecommerce.user.UserQueryRepository; -import io.jsonwebtoken.Claims; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; + +/** + * AuthHelper + * - isAuthenticatedUser + * - getAuthUserInfo + * + * JwtAuthHelper + * + * SessionAuthHelper + * + */ @Service public class AuthenticationService { - private final String AUTH_TYPE = "Bearer"; - - private final JwtUtils jwtUtils; - private final UserQueryRepository userQueryRepository; + private final AuthHelper authHelper; + @Autowired - public AuthenticationService(JwtUtils jwtUtils, UserQueryRepository userQueryRepository) { - this.jwtUtils = jwtUtils; + public AuthenticationService( + UserQueryRepository userQueryRepository, + AuthHelper authHelper + ) { this.userQueryRepository = userQueryRepository; - } - - // TODO : 테스트용으로 일단 간단하게 구현함. - public String login(int userId) { - return jwtUtils.encode(userId); + this.authHelper = authHelper; } /** * isAuthenticatedUser는 HTTP 요청이 인증된 사용자에 의한 것인지를 판단하는 함수이다. */ public boolean isAuthenticatedUser(HttpServletRequest request) { - String authorization = request.getHeader("Authorization"); - - if(authorization == null || authorization.isBlank()) { - return false; - } - - if(!isValidAuthType(authorization)) { - return false; - } - - String accessToken = extractAccessToken(authorization); - - if(accessToken == null || accessToken.isBlank()) { - return false; - } - - AuthUserInfo authUserInfo = parseAccessToken(accessToken); - - if(authUserInfo == null) { - return false; - } - - boolean hasUserId = userQueryRepository.hasUserId(authUserInfo.getUserId()); - - if(!hasUserId) { - return false; - } - - return true; + return authHelper.isAuthenticatedUser(request, userQueryRepository); } /** @@ -67,52 +44,7 @@ public boolean isAuthenticatedUser(HttpServletRequest request) { * 각 단계별로 유효성 검사를 할 수 있겠지만, 다른 기능 구현에 집중하기 위해 시간 관계상 * AuthInterceptor 에서 authorization 의 유효성이 모두 유효하게 판단된 상황을 가정해서 따로 추가하지 않았다. */ - public AuthUserInfo parseAuthorization(String authorization) { - String accessToken = extractAccessToken(authorization); - - AuthUserInfo authUserInfo = parseAccessToken(accessToken); - - return authUserInfo; - } - - /** - * isValidAuthType 는 HTTP Header 의 authorization 의 인증 유형이 유효한 인증 유형인지를 판단하는 함수이다. - */ - private boolean isValidAuthType(String authorization) { - return authorization.startsWith(AUTH_TYPE); - } - - /** - * isValidAuthType 는 HTTP Header 의 authorization로부터 accessToken을 추춣하는 함수이다. - */ - private String extractAccessToken(String authorization) { - String[] authorizationUnit = authorization.split(AUTH_TYPE+" "); - - if(authorizationUnit.length < 2) { - return null; - } - - String accessToken = authorizationUnit[1]; - - if(accessToken.isBlank()) { - return null; - } - - return accessToken; - } - - /** - * parseAccessToken 는 accessToken에 파싱하여 저장된 사용자 인증 정보를 리턴하는 함수이다. - */ - private AuthUserInfo parseAccessToken(String accessToken) { - Claims claims = jwtUtils.decode(accessToken); - - if(claims == null) { - return null; - } - - int userId = claims.get("userId", Integer.class); - - return new AuthUserInfo(userId); + public AuthUserInfo getAuthUserInfo(String authInfo) { + return authHelper.getAuthUserInfo(authInfo); } } diff --git a/src/test/java/com/hcommerce/heecommerce/auth/AuthenticationServiceTest.java b/src/test/java/com/hcommerce/heecommerce/auth/AuthenticationServiceTest.java index c4be83d..bc3da40 100644 --- a/src/test/java/com/hcommerce/heecommerce/auth/AuthenticationServiceTest.java +++ b/src/test/java/com/hcommerce/heecommerce/auth/AuthenticationServiceTest.java @@ -4,12 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.BDDMockito.given; -import com.hcommerce.heecommerce.common.utils.JwtUtils; import com.hcommerce.heecommerce.fixture.AuthFixture; -import com.hcommerce.heecommerce.fixture.JwtFixture; import com.hcommerce.heecommerce.fixture.UserFixture; import com.hcommerce.heecommerce.user.UserQueryRepository; import org.junit.jupiter.api.BeforeEach; @@ -25,9 +22,6 @@ @DisplayName("AuthenticationService") class AuthenticationServiceTest { - @Mock - private JwtUtils jwtUtils; - @Value("${jwt.secret}") private String secret; @@ -37,6 +31,9 @@ class AuthenticationServiceTest { @Mock private UserQueryRepository userQueryRepository; + @Mock + private AuthHelper authHelper; + private MockHttpServletRequest request; @BeforeEach @@ -57,9 +54,7 @@ void It_returns_true() { // given request.addHeader("Authorization", AuthFixture.AUTHORIZATION); - given(jwtUtils.decode(any())).willReturn(JwtFixture.CLAIMS); - - given(userQueryRepository.hasUserId(anyInt())).willReturn(true); + given(authHelper.isAuthenticatedUser(any(), any())).willReturn(true); // when boolean result = authenticationService.isAuthenticatedUser(request); @@ -70,93 +65,15 @@ void It_returns_true() { } @Nested - @DisplayName("when authorization is empty") - class Context_When_Authorization_Is_Empty { + @DisplayName("with invalid authorization") + class Context_With_Invalid_Authorization { @Test @DisplayName("returns false") void It_returns_false() { - // given - request.addHeader("Authorization", ""); - - given(jwtUtils.decode(any())).willReturn(JwtFixture.CLAIMS); - - given(userQueryRepository.hasUserId(anyInt())).willReturn(true); - - // when - boolean result = authenticationService.isAuthenticatedUser(request); - - // then - assertFalse(result); - } - } - - @Nested - @DisplayName("with invalid authType") - class Context_With_Invalid_AuthType { - @Test - @DisplayName("returns false") - void It_returns_false() { - // given - request.addHeader("Authorization", AuthFixture.AUTHORIZATION_WITH_INVALID_AUTH_TYPE); - - // when - boolean result = authenticationService.isAuthenticatedUser(request); - - // then - assertFalse(result); - } - } - - // accessToken - @Nested - @DisplayName("with invalid accessToken") - class Context_With_Invalid_AccessToken { - @Test - @DisplayName("returns false") - void It_returns_false() { - // given - request.addHeader("Authorization", AuthFixture.AUTHORIZATION_WITHOUT_TOKEN); - - // when - boolean result = authenticationService.isAuthenticatedUser(request); - - // then - assertFalse(result); - } - } - - @Nested - @DisplayName("with invalid authUserInfo") - class Context_With_Invalid_AuthUserInfo { - @Test - @DisplayName("returns false") - void It_returns_false() { - // given - request.addHeader("Authorization", AuthFixture.AUTHORIZATION_WITHOUT_TOKEN_PAYLOAD); - - given(jwtUtils.decode(any())).willReturn(null); - - // when - boolean result = authenticationService.isAuthenticatedUser(request); - - // then - assertFalse(result); - } - } - // authUserInfo - - @Nested - @DisplayName("with invalid userId") - class Context_With_Invalid_UserId { - @Test - @DisplayName("returns true") - void It_returns_true() { // given request.addHeader("Authorization", AuthFixture.AUTHORIZATION); - given(jwtUtils.decode(any())).willReturn(JwtFixture.CLAIMS); - - given(userQueryRepository.hasUserId(anyInt())).willReturn(false); + given(authHelper.isAuthenticatedUser(any(), any())).willReturn(false); // when boolean result = authenticationService.isAuthenticatedUser(request); @@ -168,14 +85,14 @@ void It_returns_true() { } @Nested - @DisplayName("parseAuthorization") - class Describe_ParseAuthorization { + @DisplayName("getAuthUserInfo") + class Describe_GetAuthUserInfo { @Test @DisplayName("returns tokenPayload with `userId`") void It_returns_TokenPayload() { - given(jwtUtils.decode(any())).willReturn(JwtFixture.CLAIMS); + given(authHelper.getAuthUserInfo(any())).willReturn(new AuthUserInfo(1)); - AuthUserInfo authUserInfo = authenticationService.parseAuthorization(AuthFixture.AUTHORIZATION); + AuthUserInfo authUserInfo = authenticationService.getAuthUserInfo(AuthFixture.AUTHORIZATION); assertEquals(authUserInfo.getUserId(), UserFixture.ID); } From a302d6063396998fb758b80ace258efc3d66d8a1 Mon Sep 17 00:00:00 2001 From: daadaadaah Date: Tue, 22 Aug 2023 20:11:05 +0900 Subject: [PATCH 4/4] =?UTF-8?q?`AuthenticationService`=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=97=90=20=EB=94=B0=EB=A5=B8=20`OrderController`=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/hcommerce/heecommerce/order/OrderController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/hcommerce/heecommerce/order/OrderController.java b/src/main/java/com/hcommerce/heecommerce/order/OrderController.java index b53c754..60eb395 100644 --- a/src/main/java/com/hcommerce/heecommerce/order/OrderController.java +++ b/src/main/java/com/hcommerce/heecommerce/order/OrderController.java @@ -41,7 +41,7 @@ public ResponseDto placeOrderInAdvance( @RequestHeader(value = "Authorization") String authorization, @Valid @RequestBody OrderFormDto orderFormDto ) { - AuthUserInfo authUserInfo = authenticationService.parseAuthorization(authorization); + AuthUserInfo authUserInfo = authenticationService.getAuthUserInfo(authorization); OrderForm orderForm = OrderForm.of(orderFormDto, authUserInfo.getUserId());