1. Introduction

Spring Security is the standard for securing Spring-based applications. It has several features to manage user’s authentication, including login and logout.

In this tutorial, we’ll focus on manual logout with Spring Security.

We’ll assume that readers already understand the standard Spring Security logout process.

2. Basic Logout

When a user attempts a logout, it has several consequences on its current session state. We need to destroy the session with two steps:

  1. Invalidate HTTP session information.
  2. Clear SecurityContext as it contains authentication information.

Those two actions are performed by the SecurityContextLogoutHandler.

Let’s see that in action:

@Configuration
public class DefaultLogoutConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
          .logout(logout -> logout
            .logoutUrl("/basic/basiclogout")
            .addLogoutHandler(new SecurityContextLogoutHandler())
          );
        return http.build();
    }
}

Note that SecurityContextLogoutHandler is added by Spring Security by default – we just show it here for clarity.

Often, a logout also requires us to clear some or all of a user’s cookies.

To do that, we can create our own LogoutHandler that loops through all cookies and expires them on logout:

@Configuration
public class AllCookieClearingLogoutConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
          .logout(logout -> logout
            .logoutUrl("/cookies/cookielogout")
            .addLogoutHandler((request, response, auth) -> {
                for (Cookie cookie : request.getCookies()) {
                    String cookieName = cookie.getName();
                    Cookie cookieToDelete = new Cookie(cookieName, null);
                    cookieToDelete.setMaxAge(0);
                    response.addCookie(cookieToDelete);
                }
            })
          );
        return http.build();
    }
}

In fact, Spring Security provides CookieClearingLogoutHandler which is a ready-to-use logout handler for cookie removal.

4. Clear-Site-Data Header Logout

Likewise, we can use a special HTTP response header to achieve the same thing; this is where the Clear-Site-Data header comes into play.

Basically, the Clear-Data-Site header clears browsing data (cookies, storage, cache) associated with the requesting website:

@Configuration
public class ClearSiteDataHeaderLogoutConfiguration {

    private static final ClearSiteDataHeaderWriter.Directive[] SOURCE = 
      {CACHE, COOKIES, STORAGE, EXECUTION_CONTEXTS};

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
          .logout(logout -> logout
            .logoutUrl("/csd/csdlogout")
            .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(SOURCE)))
          );
        return http.build();
    }
}

However, storage cleansing might corrupt the application state when we clear only one type of storage. Therefore, due to Incomplete Clearing, the header is only applied if the request is secure.

5. Logout on Request

Similarly, we can use HttpServletRequest.logout() method to log a user out.

Firstly, let’s add the necessary configuration to manually call logout() on the request:

@Configuration
public static class LogoutOnRequestConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.antMatcher("/request/**")
            .authorizeRequests(authz -> authz.anyRequest()
                .permitAll())
            .logout(logout -> logout.logoutUrl("/request/logout")
                .addLogoutHandler((request, response, auth) -> {
                    try {
                        request.logout();
                    } catch (ServletException e) {
                        logger.error(e.getMessage());
                    }
                }));
        return http.build();
    }
}

Finally, let’s create a test case to confirm that everything works as expected:

@Test
public void givenLoggedUserWhenUserLogoutOnRequestThenSessionCleared() throws Exception {

    this.mockMvc.perform(post("/request/logout").secure(true)
        .with(csrf()))
        .andExpect(status().is3xxRedirection())
        .andExpect(unauthenticated())
        .andReturn();
}

6. Conclusion

In summary, Spring Security has many built-in features to handle authentication scenarios. It always comes in handy to master how to use those features programmatically.

As always, the code for these examples is available over on GitHub.