Manual Logout With Spring Security – 使用Spring Security手动注销

最后修改: 2020年 5月 5日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

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

Spring Security是保护基于Spring的应用程序的标准。它有几个功能来管理用户的认证,包括登录和注销。

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

在本教程中,我们将重点讨论使用Spring Security手动注销。

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

我们将假设读者已经了解了标准的Spring Security logout流程。

2. Basic Logout

2.基本注销

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.

这两个动作是由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.

请注意,SecurityContextLogoutHandler是由Spring Security默认添加的–我们只是在这里展示它,以示清楚。

3. Cookie Clearing Logout

3.清除Cookie注销

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

通常,注销也要求我们清除用户的部分或全部cookies。

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

要做到这一点,我们可以创建我们自己的LogoutHandler,循环浏览所有的cookie,并在注销时将其失效。

@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.

事实上,Spring Security提供了CookieClearingLogoutHandler,它是一个现成的用于移除cookie的注销处理程序。

4. Clear-Site-Data Header Logout

4.清除网站数据标题注销

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.

同样,我们可以使用一个特殊的HTTP响应头来实现同样的事情;这就是Clear-site-Data header发挥作用的地方。

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

基本上,Clear-Data-Site标头会清除与请求网站相关的浏览数据(cookies、存储、缓存)。

@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

5.按要求注销

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

同样,我们可以使用HttpServletRequest.logout()方法来注销一个用户。

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

首先,让我们添加必要的配置,在请求中手动调用logout()

@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

6.结语

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.

综上所述,Spring Security有许多内置功能来处理认证场景。掌握如何以编程方式使用这些功能总是很方便的。

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

像往常一样,这些例子的代码可以在GitHub上找到over