Redirect from CustomLogoutHandler to 'login?logout' is redirected to 'login'

Viewed 641

I have two secured areas for which I provide form-based logins.

  • /user
  • /admin

The following works as expected:

  • Redirecting to Login-Page when accessing protected resource
  • Redirecting to Login-Page with an ?error param when credentials were false
  • Preventing access to protected resource based on roles defined

The following does not work:

  • Redirecting to Login-Page after logging out

When logging out, I redirect to /user/login?logout in order to display a message 'You'e logged out' indicating that logout was successful. However, that does not work, instead the redirect to /user/login?logout is being redirected to /user/login and thus no message is shown.

When I remove the custom logout-handler .logoutSuccessHandler(logoutSuccessHandler()), and instead include the .logoutSuccessUrl("/user/login?logout").permitAll() it works!

However I want this handler to perform additional actions when logging out.

@Configuration
@Order(1)
public static class FormLoginUser extends WebSecurityConfigurerAdapter {

    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return new UserCustomAccessDeniedHandler();
    }

    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return new UserCustomLogoutSuccessHandler();
    }

    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
                return new UserCustomAuthenticationSuccessHandler();
    }
    private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource() {
        return WebAuthenticationDetails::new;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.antMatcher("/user/**")
                .authorizeRequests().anyRequest().hasRole("USER")
                .and()
                .formLogin()
                .loginPage("/user/login")
                .permitAll()
                .authenticationDetailsSource(authenticationDetailsSource())
                .successHandler(authenticationSuccessHandler())
                .and()
                .logout()
                .logoutUrl("/user/logout")
                .logoutSuccessUrl("/user/login?logout").permitAll()
                .logoutSuccessHandler(logoutSuccessHandler())
                .and()
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler())
        ;

    }
}

@Configuration
@Order(2)
public static class FormLoginAdmin extends WebSecurityConfigurerAdapter {

    @Bean
    public AccessDeniedHandler adminAccessDeniedHandler() {
        return new AdminCustomAccessDeniedHandler();
    }

    @Bean
    public LogoutSuccessHandler adminLogoutSuccessHandler() {
        return new AdminCustomLogoutSuccessHandler();
    }

    @Bean
    public AuthenticationSuccessHandler adminAuthenticationSuccessHandler() {
        return new AdminCustomAuthenticationSuccessHandler();
    }
    private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource() {
        return WebAuthenticationDetails::new;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.antMatcher("/admin/**")
                .authorizeRequests().anyRequest().hasRole("ADMIN")
                .and()
                .formLogin()
                .loginPage("/admin/login")
                .authenticationDetailsSource(authenticationDetailsSource())
                .successHandler(adminAuthenticationSuccessHandler())
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/admin/logout")
                .logoutSuccessHandler(adminLogoutSuccessHandler())
                .and()
                .exceptionHandling().accessDeniedHandler(adminAccessDeniedHandler());

    }
}

Here is my CustomLogoutHandler:

public class UserCustomLogoutSuccessHandler extends
        SimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
    public void onLogoutSuccess(
            HttpServletRequest request,
            HttpServletResponse response,
            Authentication authentication)
            throws IOException, ServletException {
        this.setDefaultTargetUrl("/user/login?logout");
        super.onLogoutSuccess(request, response, authentication);
    }
}

In the DevTools, I can clearly see, whenever I try to access the URL /user/login?logout the GET-Request results in 302 and then a new request is made for user/login. That happens when I manually edit the URL in the browser URL line or when triggering the logout via FORM-Post from the application.

When I remove the logoutSuccessHandler I can both, enter the URL in the browser manually and trigger it form my FORM-Post in the application.

I also tried:

  • moving the URLs for login and logout out of the path /user -> that broke the Login
  • defining a third configuration with Order(1) explicitly allowing GET and POST on the login and logout-pages -> that as well broke the Login
  • not use .antMatcher and use .antMatchers instead, however then I think I won't be able to have two different FormLogins
1 Answers

I know this type of problems puts a big question mark and makes to scratch the head by thinking WHY?

The Solution would be.

@Override
protected void configure(HttpSecurity http) throws Exception 
{
    http
        .antMatcher("/user/**").authorizeRequests()
        .antMatchers("/user/login").permitAll() //solution
        .anyRequest().hasRole("USER")
        .and()
        .formLogin()
        .loginPage("/user/login")
        .permitAll()
        .authenticationDetailsSource(authenticationDetailsSource())
        .successHandler(authenticationSuccessHandler())
        .and()
        .logout()
        .logoutUrl("/user/logout")
        .logoutSuccessUrl("/user/login?logout").permitAll()
        .logoutSuccessHandler(logoutSuccessHandler())
        .and()
        .exceptionHandling().accessDeniedHandler(accessDeniedHandler());
}

Want to know why?

From previous configuration

Your resource /user/login is restricted resource and it will be allowed only if
authenticated = true and hasRole = "User"
In your logout success handler you are invalidating session and redirecting to /user/login?logout page but /user/login is a restricted resource so FilterSecurityInterceptor will redirects to the login page configured(.loginPage("/user/login"). Hence you will not receive any param's passed in query string.

Hence the solution will be always making login page as unrestricted resource.

Related