refactor(auth): centralize deployment path resolution

This commit is contained in:
ylhu16 2026-08-06 10:02:19 +08:00
parent 34f244e7a4
commit 647d7271a5
4 changed files with 42 additions and 14 deletions

View file

@ -1,5 +1,6 @@
package com.iflytek.skillhub.compat;
import com.iflytek.skillhub.auth.oauth.OAuthLoginRedirectSupport;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@ -11,14 +12,8 @@ import java.util.Map;
*/
@RestController
public class WellKnownController {
@GetMapping("/.well-known/clawhub.json")
public Map<String, String> clawhubConfig(HttpServletRequest request) {
// Honor the deployment sub-path so CLI clients discover /<prefix>/api/v1 instead of
// the domain-root /api/v1. With forward-headers-strategy=framework, an upstream
// X-Forwarded-Prefix is reflected into the request context path.
String contextPath = request.getContextPath();
String prefix = (contextPath == null) ? "" : contextPath;
return Map.of("apiBase", prefix + "/api/v1");
return Map.of("apiBase", OAuthLoginRedirectSupport.apiBase(request));
}
}

View file

@ -4,6 +4,7 @@ import com.iflytek.skillhub.auth.oauth.CustomOAuth2UserService;
import com.iflytek.skillhub.auth.oauth.CustomOidcUserService;
import com.iflytek.skillhub.auth.oauth.OAuth2LoginFailureHandler;
import com.iflytek.skillhub.auth.oauth.OAuth2LoginSuccessHandler;
import com.iflytek.skillhub.auth.oauth.OAuthLoginRedirectSupport;
import com.iflytek.skillhub.auth.oauth.SkillHubOAuth2AuthorizationRequestResolver;
import com.iflytek.skillhub.auth.mock.MockAuthFilter;
import com.iflytek.skillhub.auth.policy.RouteSecurityPolicyRegistry;
@ -152,12 +153,8 @@ public class SecurityConfig {
)
.logout(logout -> logout
.logoutUrl("/api/v1/auth/logout")
// Redirect to the deployment root honoring any sub-path prefix (X-Forwarded-Prefix
// is reflected into the context path), so logout does not escape a sub-path deployment.
.logoutSuccessHandler((request, response, authentication) -> {
String contextPath = request.getContextPath();
response.sendRedirect(((contextPath == null) ? "" : contextPath) + "/");
})
.logoutSuccessHandler((request, response, authentication) ->
response.sendRedirect(OAuthLoginRedirectSupport.webRoot(request)))
.invalidateHttpSession(true)
.deleteCookies("SESSION")
)

View file

@ -1,7 +1,10 @@
package com.iflytek.skillhub.auth.oauth;
import jakarta.servlet.http.HttpServletRequest;
/**
* Utility methods and constants for safely handling post-login redirect targets in OAuth flows.
* Utility methods and constants for safely handling OAuth redirect targets and
* deployment-path aware browser-visible URLs.
*/
public final class OAuthLoginRedirectSupport {
@ -11,6 +14,30 @@ public final class OAuthLoginRedirectSupport {
private OAuthLoginRedirectSupport() {
}
public static String apiBase(HttpServletRequest request) {
return deploymentPrefix(request) + "/api/v1";
}
public static String webRoot(HttpServletRequest request) {
return deploymentPrefix(request) + "/";
}
static String deploymentPrefix(HttpServletRequest request) {
if (request == null) {
return "";
}
String rawPrefix = request.getContextPath();
if (rawPrefix == null || rawPrefix.isBlank() || "/".equals(rawPrefix)) {
return "";
}
String prefix = rawPrefix.endsWith("/") ? rawPrefix.substring(0, rawPrefix.length() - 1) : rawPrefix;
if (!prefix.startsWith("/") || prefix.contains("//") || prefix.contains("\\")
|| prefix.contains("?") || prefix.contains("#")) {
return "";
}
return prefix;
}
public static String sanitizeReturnTo(String candidate) {
if (candidate == null || candidate.isBlank()) {
return null;

View file

@ -108,6 +108,15 @@ class OAuth2LoginHandlersTest {
assertThat(response.getRedirectedUrl()).isEqualTo("/skillhub/dashboard/publish");
}
@Test
void deploymentPathSupport_returnsPrefixedApiAndWebRoot() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContextPath("/skillhub");
assertThat(OAuthLoginRedirectSupport.apiBase(request)).isEqualTo("/skillhub/api/v1");
assertThat(OAuthLoginRedirectSupport.webRoot(request)).isEqualTo("/skillhub/");
}
/**
* Regression test: when an unauthenticated client hits a protected API endpoint, Spring Security
* caches that request. With {@code SavedRequestAwareAuthenticationSuccessHandler} the post-login