Robert Strauch
Robert Strauch

Reputation: 12876

Logout via Spring Cloud Gateway does not work with Spring Security, OIDC and Keycloak

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:

  1. SPA sends a POST to /logout on the gateway.
  2. Gateway invalidates the session and its SESSION cookie.
  3. Gateway contacts Keycloak's end_session_endpoint, i.e. http://localhost:8080/auth/realms/demo/protocol/openid-connect/logout.
  4. User gets redirected to the SPA.

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:

  1. Keycloak seems to still have a session for this user. The Keycloak cookies are still there and a reload of the SPA directly creates a new session based on these cookies.
  2. The POST to /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"?

Keycloak cookies in browser

Upvotes: 1

Views: 3451

Answers (1)

sdoxsee
sdoxsee

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

Related Questions