From 026797c9fadd2e37dc85bdd382177677d227138c Mon Sep 17 00:00:00 2001 From: wurongjie Date: Thu, 9 Apr 2026 10:17:10 +0800 Subject: [PATCH 1/2] fix(auth): use SimpleUrlAuthenticationSuccessHandler for OAuth2 login Replace SavedRequestAwareAuthenticationSuccessHandler with SimpleUrlAuthenticationSuccessHandler to prevent redirecting to saved API requests after OAuth2 login. Previously, when a user accessed a protected API endpoint (e.g., /api/web/skills) without authentication, Spring Security would save that request. After OAuth2 login, the handler would redirect back to the API endpoint instead of the dashboard. Now the handler only uses: - returnTo parameter from session (if present) - default target URL (/dashboard) as fallback --- .../auth/oauth/OAuth2LoginSuccessHandler.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginSuccessHandler.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginSuccessHandler.java index b87c0018e..fafce2ea8 100644 --- a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginSuccessHandler.java +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginSuccessHandler.java @@ -1,23 +1,29 @@ package com.iflytek.skillhub.auth.oauth; +import java.io.IOException; + +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + import com.iflytek.skillhub.auth.rbac.PlatformPrincipal; import com.iflytek.skillhub.auth.session.PlatformSessionService; + import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import org.springframework.stereotype.Component; - -import java.io.IOException; /** * Login success handler that copies the resolved platform principal into the - * HTTP session and then redirects to the stored return target. + * HTTP session and then redirects to the stored return target or default URL. + * + *

This handler extends {@link SimpleUrlAuthenticationSuccessHandler} and only + * uses the returnTo parameter stored in session and the default target URL for + * redirect decisions, ignoring any saved request from Spring Security's RequestCache. */ @Component -public class OAuth2LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { +public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final PlatformSessionService platformSessionService; private final OAuthLoginFlowService oauthLoginFlowService; @@ -41,9 +47,10 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String returnTo = oauthLoginFlowService.consumeReturnTo(request.getSession(false)); if (returnTo != null) { getRedirectStrategy().sendRedirect(request, response, returnTo); - clearAuthenticationAttributes(request); return; } + + // Use default target URL (/dashboard) super.onAuthenticationSuccess(request, response, authentication); } } From 16704ffa060949c2aa63e51a2d4aaf1c94cc8991 Mon Sep 17 00:00:00 2001 From: xiose Date: Tue, 19 May 2026 09:50:07 +0800 Subject: [PATCH 2/2] test(auth): add regression for OAuth2 success redirect; restore clearAuthenticationAttributes Cover the no-returnTo + cached-API-request branch with HttpSessionRequestCache so the original bug (post-login redirect resolving to /api/web/skills) cannot be silently reintroduced. Also restore clearAuthenticationAttributes() in the returnTo branch so it stays symmetric with the default branch (super clears it). --- .../auth/oauth/OAuth2LoginSuccessHandler.java | 4 +- .../auth/oauth/OAuth2LoginHandlersTest.java | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginSuccessHandler.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginSuccessHandler.java index fafce2ea8..adc5c37bb 100644 --- a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginSuccessHandler.java +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginSuccessHandler.java @@ -47,10 +47,10 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String returnTo = oauthLoginFlowService.consumeReturnTo(request.getSession(false)); if (returnTo != null) { getRedirectStrategy().sendRedirect(request, response, returnTo); + // The default branch below clears these via super; clear here too so both paths behave consistently. + clearAuthenticationAttributes(request); return; } - - // Use default target URL (/dashboard) super.onAuthenticationSuccess(request, response, authentication); } } diff --git a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginHandlersTest.java b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginHandlersTest.java index 592151fa1..8d89c8a42 100644 --- a/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginHandlersTest.java +++ b/server/skillhub-auth/src/test/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginHandlersTest.java @@ -10,6 +10,7 @@ import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import java.util.List; import java.util.Map; @@ -59,6 +60,42 @@ void successHandler_redirectsToStoredReturnTo() throws Exception { assertThat(session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)).isNotNull(); } + /** + * Regression test: when an unauthenticated client hits a protected API endpoint, Spring Security + * caches that request. With {@code SavedRequestAwareAuthenticationSuccessHandler} the post-login + * redirect would resolve to the cached API URL, leaving the user staring at raw JSON instead of + * the dashboard. The handler must ignore the saved request and fall back to the default target. + */ + @Test + void successHandler_ignoresSavedApiRequestAndRedirectsToDefault() throws Exception { + OAuthLoginFlowService oauthLoginFlowService = mock(OAuthLoginFlowService.class); + OAuth2LoginSuccessHandler handler = new OAuth2LoginSuccessHandler( + new com.iflytek.skillhub.auth.session.PlatformSessionService(), + oauthLoginFlowService + ); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setMethod("GET"); + request.setRequestURI("/api/web/skills"); + MockHttpServletResponse response = new MockHttpServletResponse(); + // Simulate Spring Security saving the API request that triggered login. + new HttpSessionRequestCache().saveRequest(request, response); + + var principal = new com.iflytek.skillhub.auth.rbac.PlatformPrincipal( + "user-1", "User", "user@example.com", null, "github", Set.of() + ); + Authentication authentication = new UsernamePasswordAuthenticationToken( + new DefaultOAuth2User(List.of(), Map.of("platformPrincipal", principal, "login", "user"), "login"), + null, + List.of() + ); + org.mockito.Mockito.when(oauthLoginFlowService.consumeReturnTo(org.mockito.ArgumentMatchers.any())) + .thenReturn(null); + + handler.onAuthenticationSuccess(request, response, authentication); + + assertThat(response.getRedirectedUrl()).isEqualTo("/dashboard"); + } + @Test void failureHandler_redirectsBackToLoginWithReturnTo() throws Exception { OAuthLoginFlowService oauthLoginFlowService = mock(OAuthLoginFlowService.class);