8

Currently we have a problem with Spring CSRF solution for our legacy App because CSRF implementation changes behavior of default Spring security Spring security configuration sis following:

<http pattern="">
...
<logout
                logout-url="/logout"
                delete-cookies="..."
                success-handler-ref="logoutSuccessHandler"
                />
<csrf/>
</http>

org.springframework.security.config.annotation.web.configurers.LogoutConfigurer Logout configurer. According to Spring documentation:

Adding CSRF will update the LogoutFilter to only use HTTP POST. This ensures that log out requires a CSRF token and that a malicious user cannot forcibly log out your users.

Code that makes this change is the following:

 private RequestMatcher getLogoutRequestMatcher(H http) {
        if(logoutRequestMatcher != null) {
            return logoutRequestMatcher;
        }
        if(http.getConfigurer(CsrfConfigurer.class) != null) {
            this.logoutRequestMatcher = new AntPathRequestMatcher(this.logoutUrl, "POST");
        } else {
            this.logoutRequestMatcher = new AntPathRequestMatcher(this.logoutUrl);
        }
        return this.logoutRequestMatcher;
    }

Generally for CSRF protection such behavior makes perfect sense. But as for me it's very strange that this implementation is not flexible (why hardcode real implementations and not autowire dependencies?).

The problem is that our application is built in such way that before regular Spring logout it's performed additional clean up in Spring Controllers. Mainly it was implemented Switch Userfeature but in a custom way. So, changing logout link to perform POST is not an option because mainly clean up is performed on custom Controller.

It seems in order to use specified approach there is only one possible solution:

@RequestMapping(value = "/logout", method = RequestMethod.GET) //or it can be a post
    public String logout() {
// 1. Perform Clean up
// 2. Decide whether to logout or redirect to other page
// 3. Perform redirect based on decision
}

//If it's decided to logout this will go to this Controller method:

  @RequestMapping(value = "csrflogout", method = RequestMethod.GET)
    public void csrfLogout(){
//1 Create manual post request
//2. Copy session information
//3. Perform Post to logout URL that is specified in security xml
     }

Generally this approach is not good from code quality perspective. So, there are two questions:

  1. What is the reason to make such strict implementation in Spring and don't provide any visible possibility to override it (specifically I provided code example how it's created )?
  2. Any good alternative to fix mentioned problem.
user1459144
  • 4,439
  • 5
  • 28
  • 35
  • 1
    Why do you use a controller in the first place? In general it is better to put that kind of logic in a [LogoutHandler](http://docs.spring.io/autorepo/docs/spring-security/current/apidocs/org/springframework/security/web/authentication/logout/LogoutHandler.html) that way you integrate with Spring Security nicely instead of trying to solve it in a controller. Also a POST will still work if you after the cleanup forward (not redirect!) to the logout URL. – M. Deinum Oct 02 '14 at 05:54
  • Also there is nothing preventing you to configure the logout to handle a GET regardless of the CSFR protection, this is just the default if you override it just do it. – M. Deinum Oct 02 '14 at 05:56
  • 1
    Can you add more details how to configure logout to be handled on GET with CSRF support (just post as answer to this post)? – user1459144 Oct 02 '14 at 06:11
  • @user1459144 see here for a blue-print: https://gist.github.com/marcelstoer/c9a894a929c8fe38222a – Marcel Stör Mar 01 '16 at 12:43

3 Answers3

8

The behavior you describe is the behavior if you don't explicitly configure logout support but only enable it, it you explicitly configure it it will use that configuration instead.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .logout()
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
}

This is also documented in the reference guide.

However the real solution is imho that you shouldn't use a controller for the additional logout functionality but use a LogoutHandler instead. That will integrate nicely with Spring Security and you don't need to redirect/forward to different URLs.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • Do you know a simple way to do it in XML config ? – user1459144 Oct 02 '14 at 06:19
  • The code you posted is related to the java based configuration NOT xml. In XML csfr is disabled by default and not enabled. The `LogoutConfigurer` is only used if you use java based configuration NOT for XML configuration. So I would say your question is confusing. For XML I'm afraid you have to configure the `LogoutFilter` manually instead of using the namespace as the `LogoutBeanDefinitionParser` (which is used!) always turns to POST when csfr is enabled. – M. Deinum Oct 02 '14 at 06:27
  • Sorry I didn't indicate that Spring security XML config is used. LogoutConfigurer is used also in XML config. I will look into approach to manually configure Logout filter – user1459144 Oct 02 '14 at 06:44
  • No it isn't... The `LogoutBeanDefinitionParser` is used not the `LogoutConfigurer`. That is why it is in the `annotation` sub package the xml based ones are in the `config.http` package. – M. Deinum Oct 02 '14 at 06:52
  • Yeah approach to manually configure logout filter works fine. Thanks for suggestion – user1459144 Oct 02 '14 at 14:09
4

Basically the main complexity was in the overriding logoutFilter in the Spring Security XML context to work with default implementation of org.springframework.security.web.util.matcher.AntPathRequestMatcher (that works with "GET" not "POST" requests). In order to do this several beans were added to security xml context:

 <bean id="logoutAntPathRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
        <constructor-arg value="logout" />
    </bean>

and logout filter itself:

<bean id="logoutFilter"
    class="org.springframework.security.web.authentication.logout.LogoutFilter">
    <constructor-arg  name="logoutSuccessHandler" ref="logoutSuccessHandler"/>
    <constructor-arg  name="handlers">
        <list>
            <ref bean="securityContextLogoutHandler" />
            <ref bean="cookieClearingLogoutHandler" />
            <ref bean="csrfLogoutHandler" />
        </list>
    </constructor-arg>
    <property name="filterProcessesUrl" value="/logout"/>
    <property name="logoutRequestMatcher" ref="logoutAntPathRequestMatcher"/>
</bean>
user1459144
  • 4,439
  • 5
  • 28
  • 35
1

I saw the same error after Internet Explorer 11 update. CsrfConfigurer.class is not null and post is expected when logging out.

if(http.getConfigurer(CsrfConfigurer.class) != null) {
            this.logoutRequestMatcher = new AntPathRequestMatcher(this.logoutUrl, "POST");
        } else {
            this.logoutRequestMatcher = new AntPathRequestMatcher(this.logoutUrl);
        }

I solved my problem by bypassing the logoutfilter and insert new filter to spring security

Example is below.

    <beans:bean id="logoutAntPathRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
            <beans:constructor-arg value="/logout"/>
        </beans:bean>

        <beans:bean id="securityContextLogoutHandler" class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler">

        </beans:bean>


        <beans:bean id="cookieClearingLogoutHandler" class="org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler">
            <beans:constructor-arg value="JSESSIONID"/>
        </beans:bean>

        <beans:bean id="logoutFilter"
              class="org.springframework.security.web.authentication.logout.LogoutFilter">
            <beans:constructor-arg  name="logoutSuccessUrl" value="/login"/>
            <beans:constructor-arg  name="handlers">
                <beans:list>
                    <beans:ref bean="securityContextLogoutHandler" />
                    <beans:ref bean="cookieClearingLogoutHandler" />
                </beans:list>
            </beans:constructor-arg>
            <beans:property name="filterProcessesUrl" value="/logout"/>
            <beans:property name="logoutRequestMatcher" ref="logoutAntPathRequestMatcher"/>
        </beans:bean>


<http>
...
<sec:custom-filter ref="logoutFilter" after="LOGOUT_FILTER"/>
...
</http>
Gurkan İlleez
  • 1,503
  • 1
  • 10
  • 12
  • Actually this is the only working solution for XML configuration. logoutAntPathRequestMatcher bean and reference to it can be omitted as filterProcessesUrl property setter instantiate exactly the same matcher under the hood. – Denis Makarskiy Jun 07 '19 at 16:30