SpringSecurity中文文档(Servlet Logout Authentication Events)

Handling Logouts

在终端用户可以登录的应用程序中,他们也应该能够注销。

默认情况下,Spring Security 支持一个/logout 端点,因此不需要额外的代码。

本节的其余部分涵盖了一些供您考虑的用例:

  • 我想了解注销的架构
  • 我想自定义注销或注销成功 URI
  • 我想知道什么时候需要显式允许/logout 端点
  • 我希望在用户注销时清除 cookie、存储和/或缓存
  • 我正在使用 OAuth 2.0,我想与授权服务器协调注销
  • 我正在使用 SAML 2.0,我想与一个身份提供者协调注销
  • 我正在使用 CAS,我想与一个标识提供程序协调注销

了解注销的体系结构(Understanding Logout’s Architecture)

当您包含 Spring-boot-starter-Security 依赖项或使用@EnableWebSecurity 注释时,Spring Security 将添加其注销支持,并默认响应 GET/logout 和 POST/logout。如果请求 GET/logout,那么 Spring Security 将显示一个注销确认页面。除了为用户提供有价值的双重检查机制之外,它还提供了一种简单的方法来为 POST/注销提供所需的 CSRF 令牌。请注意,如果在配置中禁用了 CSRF 保护,则不会向用户显示注销确认页,而是直接执行注销。

在您的应用程序中,不需要使用 GET/logout 来执行注销。只要请求中存在所需的 CSRF 令牌,应用程序就可以简单地 POST/logout 来引发注销。

如果您请求 POST/logout,那么它将使用一系列 LogoutHandlers 执行以下默认操作:

  • 使 HTTP 会话无效(SecurityContextLogoutHandler)
  • 清除 SecurityContextHolderStrategy (SecurityContextLogoutHandler)
  • 清除 SecurityContextRepository (SecurityContextLogoutHandler)
  • 清理任何 RememberMe 身份验证(TokenRememberMeServices/PersisentTokenRememberMeServices)
  • 清除所有保存的 CSRF 令牌(CsrfLogoutHandler)
  • 触发 LogoutSuccess 事件(LogoutSuccess EventPublishingLogoutHandler)

Customizing Logout URIs

由于 LogoutFilter 出现在筛选器链中的 AuthorizationFilter 之前,因此默认情况下不需要显式允许/logout 端点。因此,通常只有您自己创建的自定义注销端点才需要 permitAll 配置。

例如,如果您想简单地更改 Spring Security 匹配的 URI,您可以通过以下方式在logout DSL 中这样做:

Custom Logout Uri

http
    .logout((logout) -> logout.logoutUrl("/my/logout/uri"))

而且不需要更改授权,因为它只是调整 LogoutFilter。

但是,如果您使用自己的注销成功端点(或者在极少数情况下,使用自己的注销端点) ,比如使用 Spring MVC,则需要在 Spring Security 中允许它。这是因为 SpringMVC 在 SpringSecurity 之后处理您的请求。

您可以使用 AuthorizeHttpRequest 或 < intercept-url > 来完成这项工作,如下所示:

Custom Logout Endpoint

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/my/success/endpoint").permitAll()
        // ...
    )
    .logout((logout) -> logout.logoutSuccessUrl("/my/success/endpoint"))

在本例中,告诉 LogoutFilter 在完成后重定向到/my/Success/endpoint。并且,您显式允许 AuthorizationFilter 中的/my/Success/endpoint 端点。

不过,指定两次可能会很麻烦。如果您正在使用 Java 配置,您可以在注销 DSL 中设置 permitAll 属性,如下所示:

Permitting Custom Logout Endpoints

http
    .authorizeHttpRequests((authorize) -> authorize
        // ...
    )
    .logout((logout) -> logout
        .logoutSuccessUrl("/my/success/endpoint")
        .permitAll()
    )

它将为您将所有注销 URI 添加到许可列表中。

Adding Clean-up Actions

如果使用 Java 配置,可以通过调用 logout DSL 中的 addLogoutHandler 方法添加自己的清理操作,如下所示:

Custom Logout Handler

CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
http
    .logout((logout) -> logout.addLogoutHandler(cookies))

因为 LogoutHandler 用于清理目的,所以它们不应该引发异常。

因为 LogoutHandler 是一个函数式接口,所以您可以提供一个定制的 lambda 接口。

一些注销处理程序配置非常常见,它们可以直接在注销 DSL 和 < logout > 元素中公开。一个例子是配置会话失效,另一个例子是应该删除哪些附加 cookie。

例如,您可以配置 CookieClearingLogoutHandler,如上所示。

或者您可以像下面这样设置适当的配置值:

http
    .logout((logout) -> logout.deleteCookies("our-custom-cookie"))

指定不需要 JSESSIONID cookie,因为 SecurityContextLogoutHandler 通过使会话无效来删除它。

Using Clear-Site-Data to Log Out the User

Clear-Site-Data HTTP 头是浏览器支持的一个头,作为清除属于所有者网站的 cookie、存储和缓存的指令。这是一种方便和安全的方法,可以确保在注销时清理所有内容,包括会话 cookie。

您可以添加 configure Spring Security 来编写注销时的 Clear-Site-Data 头文件,如下所示:

Using Clear-Site-Data

HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
http
    .logout((logout) -> logout.addLogoutHandler(clearSiteData))

您向 ClearSiteDataHeaderWriter 构造函数提供希望清除的内容的列表。

上面的配置清除了所有站点数据,但是您也可以配置它删除 Cookie,如下所示:

Using Clear-Site-Data to Clear Cookies

HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.COOKIES));
http
    .logout((logout) -> logout.addLogoutHandler(clearSiteData))

Customizing Logout Success

虽然在大多数情况下使用 logoutSuccess URL 就足够了,但是一旦注销完成,您可能需要做一些不同于重定向到 URL 的事情。

LogoutSuccess Handler 是用于自定义注销成功操作的 Spring Security 组件。

例如,您可能希望只返回状态代码,而不是重定向。在这种情况下,您可以提供一个成功处理程序实例,如下所示:

Using Clear-Site-Data to Clear Cookies

http
    .logout((logout) -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))

因为 LogoutSuccess Handler 是一个函数式接口,所以您可以提供一个自定义接口作为 lambda。

Creating a Custom Logout Endpoint

强烈建议您使用提供的注销 DSL 来配置注销。一个原因是很容易忘记调用所需的 Spring Security 组件来确保正确和完整的注销。

实际上,注册一个自定义 LogoutHandler 通常比创建一个 Spring MVC 端点执行注销要简单。

也就是说,如果您发现自己处于需要自定义注销端点的环境中,如下所示:

Custom Logout Endpoint

@PostMapping("/my/logout")
public String performLogout() {
    // .. perform logout
    return "redirect:/home";
}

然后,需要让该端点调用 Spring Security 的 SecurityContextLogoutHandler 来确保安全和完整的注销。至少需要以下内容:

Custom Logout Endpoint

SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();

@PostMapping("/my/logout")
public String performLogout(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
    // .. perform logout
    this.logoutHandler.doLogout(request, response, authentication);
    return "redirect:/home";
}

这将根据需要清除 SecurityContextHolderStrategy 和 SecurityContextRepository。

另外,您还需要显式地允许端点。

未能调用 SecurityContextLogoutHandler 意味着 SecurityContext 仍然可用于后续请求,这意味着用户实际上没有注销。

Testing Logout

一旦配置了注销,就可以使用 SpringSecurity 的 MockMvc 支持对其进行测试。

Further Logout-Related References

Authentication Events

对于每个成功或失败的身份验证,将分别激发一个 AuthenticationSuccess Event 或 AuthenticationfalureEvent。

要侦听这些事件,必须首先发布一个 AuthenticationEventPublisher:

@Bean
public AuthenticationEventPublisher authenticationEventPublisher
        (ApplicationEventPublisher applicationEventPublisher) {
    return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
}

然后你可以使用 Spring 的@EventListener 支持:

@Component
public class AuthenticationEvents {
	@EventListener
    public void onSuccess(AuthenticationSuccessEvent success) {
		// ...
    }

    @EventListener
    public void onFailure(AbstractAuthenticationFailureEvent failures) {
		// ...
    }
}

虽然类似于 AuthenticationSuccess Handler 和 AuthenticationfalureHandler,但是它们非常好,因为可以独立于 servlet API 使用它们。

Adding Exception Mappings

默认情况下,DefaultAuthenticationEventPublisher 为以下事件发布 AuthenticationFallureEvent:

Exception Event
BadCredentialsException AuthenticationFailureBadCredentialsEvent
UsernameNotFoundException AuthenticationFailureBadCredentialsEvent
AccountExpiredException AuthenticationFailureExpiredEvent
ProviderNotFoundException AuthenticationFailureProviderNotFoundEvent
DisabledException AuthenticationFailureDisabledEvent
LockedException AuthenticationFailureLockedEvent
AuthenticationServiceException AuthenticationFailureServiceExceptionEvent
CredentialsExpiredException AuthenticationFailureCredentialsExpiredEvent
InvalidBearerTokenException AuthenticationFailureBadCredentialsEvent

发布者进行精确的异常匹配,这意味着这些异常的子类不会产生事件。

为此,您可能希望通过 setAdditionalExceptionMaps 方法向发布者提供额外的映射:

@Bean
public AuthenticationEventPublisher authenticationEventPublisher
        (ApplicationEventPublisher applicationEventPublisher) {
    Map<Class<? extends AuthenticationException>,
        Class<? extends AbstractAuthenticationFailureEvent>> mapping =
            Collections.singletonMap(FooException.class, FooEvent.class);
    AuthenticationEventPublisher authenticationEventPublisher =
        new DefaultAuthenticationEventPublisher(applicationEventPublisher);
    authenticationEventPublisher.setAdditionalExceptionMappings(mapping);
    return authenticationEventPublisher;
}

Default Event

您还可以提供一个可以在任何 AuthenticationException 的情况下触发的 catch-all 事件:

@Bean
public AuthenticationEventPublisher authenticationEventPublisher
        (ApplicationEventPublisher applicationEventPublisher) {
    AuthenticationEventPublisher authenticationEventPublisher =
        new DefaultAuthenticationEventPublisher(applicationEventPublisher);
    authenticationEventPublisher.setDefaultAuthenticationFailureEvent
        (GenericAuthenticationFailureEvent.class);
    return authenticationEventPublisher;
}

相关推荐

  1. SpringSecurity中文文档(Servlet OAuth2)

    2024-07-09 17:10:10       24 阅读
  2. SpringSecurity中文文档(Servlet OAuth 2.0 Client)

    2024-07-09 17:10:10       19 阅读
  3. SpringSecurity中文文档(Servlet OAuth 2.0 Login)

    2024-07-09 17:10:10       19 阅读
  4. SpringSecurity

    2024-07-09 17:10:10       34 阅读
  5. Apache 开源项目文档中心 (英文 + 中文)

    2024-07-09 17:10:10       28 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-09 17:10:10       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-09 17:10:10       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-09 17:10:10       57 阅读
  4. Python语言-面向对象

    2024-07-09 17:10:10       68 阅读

热门阅读

  1. C++多线程学习笔记

    2024-07-09 17:10:10       24 阅读
  2. 实现基于Spring Cloud的事件驱动微服务

    2024-07-09 17:10:10       25 阅读
  3. js使用websocket,vue使用websocket,copy即用

    2024-07-09 17:10:10       25 阅读
  4. PostgreSQL的扩展(extensions)-常用的扩展-pg_profile

    2024-07-09 17:10:10       26 阅读
  5. Spring Boot整合MongoDB实现事务管理

    2024-07-09 17:10:10       24 阅读
  6. Solana RPC 的工作原理

    2024-07-09 17:10:10       24 阅读
  7. 音频demo:使用faad2将AAC数据解码出PCM数据

    2024-07-09 17:10:10       24 阅读
  8. SQLAlchemy配置连接多个数据库

    2024-07-09 17:10:10       29 阅读
  9. Android C++系列:Linux常用函数和工具

    2024-07-09 17:10:10       23 阅读
  10. vb.net读取mssql的image字段后,如何转换成二进制

    2024-07-09 17:10:10       29 阅读
  11. 常用 Android 反编译工具apktooldex2jarenjarifyjd-guijadx

    2024-07-09 17:10:10       23 阅读
  12. Android Gradle 开发与应用 (十): Gradle 脚本最佳实践

    2024-07-09 17:10:10       27 阅读
  13. 牛客周赛 Round 50

    2024-07-09 17:10:10       36 阅读