moar cleanup

This commit is contained in:
Dario Ghunney Ware 2025-12-15 17:59:38 +00:00
parent d296e0bd09
commit d4ac6148d8
4 changed files with 23 additions and 54 deletions

View File

@ -65,15 +65,6 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
if (!response.isCommitted()) {
if (authentication != null) {
// Check for JWT-based authentication and extract authType claim
String authType;
if (authentication instanceof JwtAuthenticationToken jwtAuth) {
authType =
(String) jwtAuth.getToken().getClaims().getOrDefault("authType", null);
log.debug("JWT-based logout detected with authType: {}", authType);
}
if (authentication instanceof OAuth2AuthenticationToken oAuthToken) {
log.info("OAuth2 logout via JWT - attempting OIDC logout");
getAuthRedirect(request, response, oAuthToken);
@ -81,7 +72,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
getAuthRedirect(request, response, samlAuthentication);
} else if (authentication
instanceof JwtAuthenticationToken jwtAuthenticationToken) {
getAuthRedirect(request, response, jwtAuthenticationToken);
getAuthRedirect(request, response);
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
// Handle Username/Password logout
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
@ -186,10 +177,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
}
// Redirect for JWT-based OAuth2 authentication logout
private void getAuthRedirect(
HttpServletRequest request,
HttpServletResponse response,
JwtAuthenticationToken jwtAuthenticationToken)
private void getAuthRedirect(HttpServletRequest request, HttpServletResponse response)
throws IOException {
OAUTH2 oauth = securityProperties.getOauth2();
String path = checkForErrors(request);
@ -213,18 +201,8 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
clientId = oauth.getClientId();
}
// Fallback: extract issuer from JWT token if not found in configuration
if (issuer == null) {
var jwtIssuer = jwtAuthenticationToken.getToken().getIssuer();
if (jwtIssuer != null) {
issuer = jwtIssuer.toString();
log.debug("Using issuer from JWT token: {}", issuer);
}
}
String endSessionEndpoint = getEndSessionEndpoint(oauth, issuer);
// If no endpoint found, try Keycloak fallback
if (endSessionEndpoint == null && issuer != null) {
endSessionEndpoint = issuer + "/protocol/openid-connect/logout";
log.debug("Using Keycloak fallback logout path: {}", endSessionEndpoint);
@ -242,7 +220,7 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
logoutUrlBuilder.append("post_logout_redirect_uri=").append(encodedRedirectUri);
String logoutUrl = logoutUrlBuilder.toString();
log.info("JWT-based OAuth2 logout URL: {}", logoutUrl);
log.debug("JWT-based OAuth2 logout URL: {}", logoutUrl);
// Return JSON for API requests, redirect for browser requests
if (isApi) {

View File

@ -1009,8 +1009,8 @@ class CustomLogoutSuccessHandlerTest {
when(request.getHeader("X-Requested-With")).thenReturn(null);
when(response.getWriter()).thenReturn(printWriter);
when(jwtAuth.getToken()).thenReturn(jwt);
when(jwt.getClaims()).thenReturn(Map.of("authType", "OAUTH2"));
lenient().when(jwtAuth.getToken()).thenReturn(jwt);
lenient().when(jwt.getClaims()).thenReturn(Map.of("authType", "OAUTH2"));
when(securityProperties.getOauth2()).thenReturn(oauth);
when(oauth.getClient()).thenReturn(client);
@ -1067,8 +1067,8 @@ class CustomLogoutSuccessHandlerTest {
when(request.getHeader("X-Requested-With")).thenReturn("XMLHttpRequest"); // XHR request
when(response.getWriter()).thenReturn(printWriter);
when(jwtAuth.getToken()).thenReturn(jwt);
when(jwt.getClaims()).thenReturn(Map.of("authType", "OAUTH2"));
lenient().when(jwtAuth.getToken()).thenReturn(jwt);
lenient().when(jwt.getClaims()).thenReturn(Map.of("authType", "OAUTH2"));
when(securityProperties.getOauth2()).thenReturn(oauth);
when(oauth.getClient()).thenReturn(client);
@ -1115,8 +1115,8 @@ class CustomLogoutSuccessHandlerTest {
when(request.getHeader("Accept")).thenReturn("text/html"); // Browser request
when(request.getHeader("X-Requested-With")).thenReturn(null);
when(jwtAuth.getToken()).thenReturn(jwt);
when(jwt.getClaims()).thenReturn(Map.of("authType", "OAUTH2"));
lenient().when(jwtAuth.getToken()).thenReturn(jwt);
lenient().when(jwt.getClaims()).thenReturn(Map.of("authType", "OAUTH2"));
when(securityProperties.getOauth2()).thenReturn(oauth);
when(oauth.getClient()).thenReturn(client);
@ -1164,8 +1164,8 @@ class CustomLogoutSuccessHandlerTest {
when(request.getHeader("X-Requested-With")).thenReturn(null);
when(response.getWriter()).thenReturn(printWriter);
when(jwtAuth.getToken()).thenReturn(jwt);
when(jwt.getClaims()).thenReturn(Map.of("authType", "OAUTH2"));
lenient().when(jwtAuth.getToken()).thenReturn(jwt);
lenient().when(jwt.getClaims()).thenReturn(Map.of("authType", "OAUTH2"));
when(securityProperties.getOauth2()).thenReturn(oauth);
when(oauth.getClient()).thenReturn(client);

View File

@ -284,15 +284,9 @@ class SpringAuthClient {
*/
async signOut(): Promise<{ error: AuthError | null }> {
try {
// Extract token before removing it (needed for OAuth/SAML logout)
// Extract token before making request (needed for OAuth/SAML logout)
const token = localStorage.getItem('stirling_jwt');
// Clean up local storage before logout
localStorage.removeItem('stirling_jwt');
// Call Spring Security's logout endpoint
// This will handle OAuth2/SAML logout and return JSON with logout URL for OIDC providers
// Include the token so Spring knows which authentication type to logout from
const response = await apiClient.post('/logout', null, {
headers: {
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
@ -301,19 +295,24 @@ class SpringAuthClient {
withCredentials: true,
});
// Notify listeners
this.notifyListeners('SIGNED_OUT', null);
// Clean up local storage after successful logout request
localStorage.removeItem('stirling_jwt');
// If we got a logout URL (for OIDC/OAuth2 providers), navigate to it
// If we got a logout URL (for OIDC/OAuth2 providers), redirect immediately
// Don't notify listeners to avoid React re-render before redirect
if (response.data?.logoutUrl) {
window.location.href = response.data.logoutUrl;
return { error: null };
}
this.notifyListeners('SIGNED_OUT', null);
window.location.href = '/login?logout=true';
return { error: null };
} catch (error: unknown) {
console.error('[SpringAuth] signOut error:', error);
// Still notify listeners even if backend call fails
localStorage.removeItem('stirling_jwt');
this.notifyListeners('SIGNED_OUT', null);
window.location.href = '/login?logout=true';
return {
error: { message: getErrorMessage(error, 'Logout failed') },
};

View File

@ -37,17 +37,9 @@ const AccountSection: React.FC = () => {
const userIdentifier = useMemo(() => user?.email || user?.username || '', [user?.email, user?.username]);
const redirectToLogin = useCallback(() => {
window.location.assign('/login');
}, []);
const handleLogout = useCallback(async () => {
try {
await signOut();
} finally {
redirectToLogin();
}
}, [redirectToLogin, signOut]);
await signOut();
}, [signOut]);
const handlePasswordSubmit = async (event: React.FormEvent) => {
event.preventDefault();