Reputation: 12876
I'm running a Spring Cloud Gateway which handles the OAuth2 authentication with Keycloak. The login part from a single page application (SPA) works fine, but now I have trouble with the logout.
Here's what I have in mind:
/logout
on the gateway.SESSION
cookie.end_session_endpoint
, i.e. http://localhost:8080/auth/realms/demo/protocol/openid-connect/logout
.This is my current security configuration with Webflux. The code is based on the examples and information mentioned here:
@Configuration
@EnableWebFluxSecurity
public class SecurityConfiguration {
private static final String FRONTEND_URL = "http://localhost:8093"
@Autowired
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) {
httpSecurity
.csrf().disable()
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2Login()
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler(FRONTEND_URL))
.and()
.exceptionHandling().authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.logout()
.logoutHandler(logoutHandler())
.logoutSuccessHandler(oidcLogoutSuccessHandler());
return httpSecurity.build();
}
private ServerLogoutHandler logoutHandler() {
return new DelegatingServerLogoutHandler(new WebSessionServerLogoutHandler(), new SecurityContextServerLogoutHandler());
}
private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedServerLogoutSuccessHandler logoutSuccessHandler = new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository);
logoutSuccessHandler.setPostLogoutRedirectUri(FRONTEND_URL);
return logoutSuccessHandler;
}
}
When sending a POST to /logout
, the debug logs show the following:
athPatternParserServerWebExchangeMatcher : Request 'POST /logout' doesn't match 'null /oauth2/authorization/{registrationId}'
athPatternParserServerWebExchangeMatcher : Request 'POST /logout' doesn't match 'null /login/oauth2/code/{registrationId}'
o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/logout', method=POST}
athPatternParserServerWebExchangeMatcher : Checking match of request : '/logout'; against '/logout'
o.s.s.w.s.u.m.OrServerWebExchangeMatcher : matched
ebSessionServerSecurityContextRepository : Found SecurityContext 'SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [foo], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_profile]], User Attributes: [{sub=a8375794-3dae-4d4f-ae11-2b17bfb03992, email_verified=false, realm_access={roles=[DEFAULT_USER]}, name=foo bar, preferred_username=foo, given_name=foo, family_name=bar, [email protected]}], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER, SCOPE_email, SCOPE_profile]]]' in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@560ddd31'
o.s.s.w.s.a.logout.LogoutWebFilter : Logging out user 'OAuth2AuthenticationToken [Principal=Name: [foo], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_profile]], User Attributes: [{sub=a8375794-3dae-4d4f-ae11-2b17bfb03992, email_verified=false, realm_access={roles=[DEFAULT_USER]}, name=Foo Bar, preferred_username=foo, given_name=foo, family_name=bar, [email protected]}], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER, SCOPE_email, SCOPE_profile]]' and transferring to logout destination
ebSessionServerSecurityContextRepository : Removed SecurityContext stored in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@560ddd31'
o.s.s.w.s.DefaultServerRedirectStrategy : Redirecting to '/login?logout'
athPatternParserServerWebExchangeMatcher : Request 'GET /login' doesn't match 'null /oauth2/authorization/{registrationId}'
athPatternParserServerWebExchangeMatcher : Request 'GET /login' doesn't match 'null /login/oauth2/code/{registrationId}'
o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/logout', method=POST}
athPatternParserServerWebExchangeMatcher : Request 'GET /login' doesn't match 'POST /logout'
o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found
a.DelegatingReactiveAuthorizationManager : Checking authorization on '/login' using org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager@1b45f1c
ebSessionServerSecurityContextRepository : No SecurityContext found in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@790f8540'
o.s.s.w.s.a.AuthorizationWebFilter : Authorization failed: Access Denied
ebSessionServerSecurityContextRepository : No SecurityContext found in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@790f8540'
So, it looks as if the gateway successfully invalidated the session. But here are two observations I don't understand:
/logout
redirects to /login?logout
which gets rejected with a 401. My understanding is, that the redirect should occur to FRONTEND_URL = "http://localhost:8093"
?Upvotes: 1
Views: 3451
Reputation: 4681
Perhaps you are doing an ajax post rather than a form post? The server can't redirect your browser to keycloak on a ajax call.
Upvotes: 2