Course – LSS – NPI (cat=Spring Security)
announcement - icon

If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:

>> LEARN SPRING SECURITY

1. Introduction

Spring Security is the standard for securing Spring-based applications. It has several features to manage user 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.

The SecurityContextLogoutHandler performs those two actions.

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 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 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 call .logout() on the request manually:

@Configuration
public static class LogoutOnRequestConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.securityMatchers("/request/**")
            .authorizeHttpRequests(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. Mastering how to use those features programmatically always comes in handy.

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

Course – LSS (cat=Security/Spring Security)

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE
res – Security (video) (cat=Security/Spring Security)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.