From 73891a418c89f13844cc41cacfa5eeb366f3ee8f Mon Sep 17 00:00:00 2001 From: seungwookc97 Date: Tue, 30 Sep 2025 11:05:49 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20OAuth2=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=9F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomOAuth2LoginSuccessHandlerTest.java | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/test/java/com/back/global/security/CustomOAuth2LoginSuccessHandlerTest.java diff --git a/src/test/java/com/back/global/security/CustomOAuth2LoginSuccessHandlerTest.java b/src/test/java/com/back/global/security/CustomOAuth2LoginSuccessHandlerTest.java new file mode 100644 index 00000000..eed9c50c --- /dev/null +++ b/src/test/java/com/back/global/security/CustomOAuth2LoginSuccessHandlerTest.java @@ -0,0 +1,113 @@ +package com.back.global.security; + +import com.back.domain.user.service.UserAuthService; +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CustomOAuth2LoginSuccessHandlerTest { + + @Mock + private UserAuthService userAuthService; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + @Mock + private Authentication authentication; + + @Mock + private SecurityUser securityUser; + + @InjectMocks + private CustomOAuth2LoginSuccessHandler handler; + + private final String frontendUrl = "http://localhost:3000"; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(handler, "frontendUrl", frontendUrl); + } + + + @Test + @DisplayName("첫 사용자는 first-user 페이지로 리다이렉트") + void redirectToFirstUser() throws Exception { + // given + Long userId = 1L; + String email = "test@example.com"; + String nickname = "testUser"; + + when(authentication.getPrincipal()).thenReturn(securityUser); + when(securityUser.getId()).thenReturn(userId); + when(securityUser.getEmail()).thenReturn(email); + when(securityUser.getNickname()).thenReturn(nickname); + when(securityUser.isFirstLogin()).thenReturn(true); + + // when + handler.onAuthenticationSuccess(request, response, authentication); + + // then + verify(userAuthService).issueTokens(response, userId, email, nickname); + verify(userAuthService).setFirstLoginFalse(userId); + verify(response).sendRedirect(frontendUrl + "/login/user/first-user"); + } + + @Test + @DisplayName("기존 사용자는 success 페이지로 리다이렉트") + void redirectToSuccess() throws Exception { + // given + Long userId = 1L; + String email = "test@example.com"; + String nickname = "testUser"; + + when(authentication.getPrincipal()).thenReturn(securityUser); + when(securityUser.getId()).thenReturn(userId); + when(securityUser.getEmail()).thenReturn(email); + when(securityUser.getNickname()).thenReturn(nickname); + when(securityUser.isFirstLogin()).thenReturn(false); + + // when + handler.onAuthenticationSuccess(request, response, authentication); + + // then + verify(userAuthService).issueTokens(response, userId, email, nickname); + verify(userAuthService, never()).setFirstLoginFalse(userId); + verify(response).sendRedirect(frontendUrl + "/login/user/success"); + } + + @Test + @DisplayName("토큰 발급이 정상적으로 호출되는지 확인") + void tokenIssuance() throws Exception { + // given + Long userId = 2L; + String email = "user@test.com"; + String nickname = "nickname"; + + when(authentication.getPrincipal()).thenReturn(securityUser); + when(securityUser.getId()).thenReturn(userId); + when(securityUser.getEmail()).thenReturn(email); + when(securityUser.getNickname()).thenReturn(nickname); + when(securityUser.isFirstLogin()).thenReturn(false); + + // when + handler.onAuthenticationSuccess(request, response, authentication); + + // then + verify(userAuthService).issueTokens(response, userId, email, nickname); + } +} From 8773b14b90c651416f8986a206db85a797c6bf5f Mon Sep 17 00:00:00 2001 From: seungwookc97 Date: Tue, 30 Sep 2025 11:07:31 +0900 Subject: [PATCH 2/3] =?UTF-8?q?test=20:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomAuthenticationFilterTest.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/test/java/com/back/global/security/CustomAuthenticationFilterTest.java diff --git a/src/test/java/com/back/global/security/CustomAuthenticationFilterTest.java b/src/test/java/com/back/global/security/CustomAuthenticationFilterTest.java new file mode 100644 index 00000000..cfe833f4 --- /dev/null +++ b/src/test/java/com/back/global/security/CustomAuthenticationFilterTest.java @@ -0,0 +1,90 @@ +package com.back.global.security; + +import com.back.global.jwt.JwtUtil; +import com.back.global.rq.Rq; +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CustomAuthenticationFilterTest { + + @Mock + private JwtUtil jwtUtil; + + @Mock + private Rq rq; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + @Mock + private FilterChain filterChain; + + @InjectMocks + private CustomAuthenticationFilter filter; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(filter, "accessTokenExpiration", 900); + SecurityContextHolder.clearContext(); + } + + @Test + @DisplayName("토큰 없으면 통과") + void noToken_Pass() throws Exception { + when(rq.getCookieValue("accessToken", "")).thenReturn(""); + + filter.doFilterInternal(request, response, filterChain); + + verify(filterChain).doFilter(request, response); + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + @DisplayName("유효한 토큰으로 인증 성공") + void validToken_Success() throws Exception { + String token = "valid.token"; + when(rq.getCookieValue("accessToken", "")).thenReturn(token); + when(jwtUtil.validateAccessToken(token)).thenReturn(true); + when(jwtUtil.getUserIdFromToken(token)).thenReturn(1L); + when(jwtUtil.getEmailFromToken(token)).thenReturn("test@test.com"); + when(jwtUtil.getNicknameFromToken(token)).thenReturn("user"); + + filter.doFilterInternal(request, response, filterChain); + + verify(filterChain).doFilter(request, response); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + assertNotNull(auth); + assertInstanceOf(SecurityUser.class, auth.getPrincipal()); + } + + @Test + @DisplayName("무효한 토큰은 통과") + void invalidToken_Pass() throws Exception { + String token = "invalid.token"; + when(rq.getCookieValue("accessToken", "")).thenReturn(token); + when(jwtUtil.validateAccessToken(token)).thenReturn(false); + + filter.doFilterInternal(request, response, filterChain); + + verify(filterChain).doFilter(request, response); + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } +} From 4bea4d21af0de480e8da81e8d861ca624760c8b3 Mon Sep 17 00:00:00 2001 From: seungwookc97 Date: Tue, 30 Sep 2025 13:49:44 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EC=BF=A0=ED=82=A4=20=ED=81=AC?= =?UTF-8?q?=EB=A1=9C=EC=8A=A4=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EA=B3=B5?= =?UTF-8?q?=EC=9C=A0=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95=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 --- src/main/java/com/back/global/rq/Rq.java | 4 + src/main/resources/application-dev.yml | 2 +- src/main/resources/application-prod.yml | 2 +- .../CustomAuthenticationFilterTest.java | 90 -------------- .../CustomOAuth2LoginSuccessHandlerTest.java | 113 ------------------ 5 files changed, 6 insertions(+), 205 deletions(-) delete mode 100644 src/test/java/com/back/global/security/CustomAuthenticationFilterTest.java delete mode 100644 src/test/java/com/back/global/security/CustomOAuth2LoginSuccessHandlerTest.java diff --git a/src/main/java/com/back/global/rq/Rq.java b/src/main/java/com/back/global/rq/Rq.java index 72f02868..28167eb2 100644 --- a/src/main/java/com/back/global/rq/Rq.java +++ b/src/main/java/com/back/global/rq/Rq.java @@ -31,6 +31,9 @@ public class Rq { @Value("${custom.cookie.same}") private String cookieSameSite; + @Value("${custom.site.cookieDomain}") + private String cookieDomain; + public User getActor() { return Optional.ofNullable( @@ -95,6 +98,7 @@ public void setCrossDomainCookie(String name, String value, int maxAge) { .maxAge(maxAge) .secure(cookieSecure) .sameSite(cookieSameSite) + .domain(cookieDomain) .httpOnly(true) .build(); resp.addHeader("Set-Cookie", cookie.toString()); diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 28ebf512..0bbfc94d 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -58,7 +58,7 @@ logging: # 쿠키 보안 설정 (HTTP 환경용) custom: cookie: - secure: true + secure: false same: "Lax" # # AI 설정 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 423e9a4a..06dbe374 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -44,8 +44,8 @@ custom: cookie: secure: true same: "None" - domain: ${custom.prod.cookieDomain} site: + cookieDomain: ".${BASE_URL}" frontUrl: "${custom.prod.frontUrl}" backUrl: "${custom.prod.backUrl}" name: ssoul diff --git a/src/test/java/com/back/global/security/CustomAuthenticationFilterTest.java b/src/test/java/com/back/global/security/CustomAuthenticationFilterTest.java deleted file mode 100644 index cfe833f4..00000000 --- a/src/test/java/com/back/global/security/CustomAuthenticationFilterTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.back.global.security; - -import com.back.global.jwt.JwtUtil; -import com.back.global.rq.Rq; -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.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.util.ReflectionTestUtils; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class CustomAuthenticationFilterTest { - - @Mock - private JwtUtil jwtUtil; - - @Mock - private Rq rq; - - @Mock - private HttpServletRequest request; - - @Mock - private HttpServletResponse response; - - @Mock - private FilterChain filterChain; - - @InjectMocks - private CustomAuthenticationFilter filter; - - @BeforeEach - void setUp() { - ReflectionTestUtils.setField(filter, "accessTokenExpiration", 900); - SecurityContextHolder.clearContext(); - } - - @Test - @DisplayName("토큰 없으면 통과") - void noToken_Pass() throws Exception { - when(rq.getCookieValue("accessToken", "")).thenReturn(""); - - filter.doFilterInternal(request, response, filterChain); - - verify(filterChain).doFilter(request, response); - assertNull(SecurityContextHolder.getContext().getAuthentication()); - } - - @Test - @DisplayName("유효한 토큰으로 인증 성공") - void validToken_Success() throws Exception { - String token = "valid.token"; - when(rq.getCookieValue("accessToken", "")).thenReturn(token); - when(jwtUtil.validateAccessToken(token)).thenReturn(true); - when(jwtUtil.getUserIdFromToken(token)).thenReturn(1L); - when(jwtUtil.getEmailFromToken(token)).thenReturn("test@test.com"); - when(jwtUtil.getNicknameFromToken(token)).thenReturn("user"); - - filter.doFilterInternal(request, response, filterChain); - - verify(filterChain).doFilter(request, response); - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - assertNotNull(auth); - assertInstanceOf(SecurityUser.class, auth.getPrincipal()); - } - - @Test - @DisplayName("무효한 토큰은 통과") - void invalidToken_Pass() throws Exception { - String token = "invalid.token"; - when(rq.getCookieValue("accessToken", "")).thenReturn(token); - when(jwtUtil.validateAccessToken(token)).thenReturn(false); - - filter.doFilterInternal(request, response, filterChain); - - verify(filterChain).doFilter(request, response); - assertNull(SecurityContextHolder.getContext().getAuthentication()); - } -} diff --git a/src/test/java/com/back/global/security/CustomOAuth2LoginSuccessHandlerTest.java b/src/test/java/com/back/global/security/CustomOAuth2LoginSuccessHandlerTest.java deleted file mode 100644 index eed9c50c..00000000 --- a/src/test/java/com/back/global/security/CustomOAuth2LoginSuccessHandlerTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.back.global.security; - -import com.back.domain.user.service.UserAuthService; -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.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.core.Authentication; -import org.springframework.test.util.ReflectionTestUtils; - -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class CustomOAuth2LoginSuccessHandlerTest { - - @Mock - private UserAuthService userAuthService; - - @Mock - private HttpServletRequest request; - - @Mock - private HttpServletResponse response; - - @Mock - private Authentication authentication; - - @Mock - private SecurityUser securityUser; - - @InjectMocks - private CustomOAuth2LoginSuccessHandler handler; - - private final String frontendUrl = "http://localhost:3000"; - - @BeforeEach - void setUp() { - ReflectionTestUtils.setField(handler, "frontendUrl", frontendUrl); - } - - - @Test - @DisplayName("첫 사용자는 first-user 페이지로 리다이렉트") - void redirectToFirstUser() throws Exception { - // given - Long userId = 1L; - String email = "test@example.com"; - String nickname = "testUser"; - - when(authentication.getPrincipal()).thenReturn(securityUser); - when(securityUser.getId()).thenReturn(userId); - when(securityUser.getEmail()).thenReturn(email); - when(securityUser.getNickname()).thenReturn(nickname); - when(securityUser.isFirstLogin()).thenReturn(true); - - // when - handler.onAuthenticationSuccess(request, response, authentication); - - // then - verify(userAuthService).issueTokens(response, userId, email, nickname); - verify(userAuthService).setFirstLoginFalse(userId); - verify(response).sendRedirect(frontendUrl + "/login/user/first-user"); - } - - @Test - @DisplayName("기존 사용자는 success 페이지로 리다이렉트") - void redirectToSuccess() throws Exception { - // given - Long userId = 1L; - String email = "test@example.com"; - String nickname = "testUser"; - - when(authentication.getPrincipal()).thenReturn(securityUser); - when(securityUser.getId()).thenReturn(userId); - when(securityUser.getEmail()).thenReturn(email); - when(securityUser.getNickname()).thenReturn(nickname); - when(securityUser.isFirstLogin()).thenReturn(false); - - // when - handler.onAuthenticationSuccess(request, response, authentication); - - // then - verify(userAuthService).issueTokens(response, userId, email, nickname); - verify(userAuthService, never()).setFirstLoginFalse(userId); - verify(response).sendRedirect(frontendUrl + "/login/user/success"); - } - - @Test - @DisplayName("토큰 발급이 정상적으로 호출되는지 확인") - void tokenIssuance() throws Exception { - // given - Long userId = 2L; - String email = "user@test.com"; - String nickname = "nickname"; - - when(authentication.getPrincipal()).thenReturn(securityUser); - when(securityUser.getId()).thenReturn(userId); - when(securityUser.getEmail()).thenReturn(email); - when(securityUser.getNickname()).thenReturn(nickname); - when(securityUser.isFirstLogin()).thenReturn(false); - - // when - handler.onAuthenticationSuccess(request, response, authentication); - - // then - verify(userAuthService).issueTokens(response, userId, email, nickname); - } -}