Handling Logouts

在最终用户可以 login 的应用程序中,他们还应该能够注销。 默认情况下,Spring Security 建立一个 /logout 端点,因此不需要其他代码。 本节的其余部分涵盖了许多你可能需要考虑的使用案例:

Understanding Logout’s Architecture

当您包含 the spring-boot-starter-security 依赖项 或使用 @EnableWebSecurity 注释时,Spring Security 将添加注销支持,并默认响应 GET /logoutPOST /logout.

如果您请求 GET /logout,则 Spring Security 将显示一个注销确认页面。除了为用户提供一个有价值的双重检查机制之外,它还提供了一种简单的方法来为 POST /logout 提供 the needed CSRF token

请注意,如果配置中禁用了 CSRF protection,则不会向用户显示注销确认页面并且将直接执行注销。

在你的应用程序中,无需使用 GET /logout 来执行注销。只要 the needed CSRF token 存在于请求中,你的应用程序就可以简单地 POST /logout 来引发注销。

如果您请求 POST /logout,那么它将使用一系列 {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[LogoutHandler] 执行以下默认操作:

  • 使 HTTP 会话无效 ( {security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[SecurityContextLogoutHandler] )

  • Clear the SecurityContextHolderStrategy ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[SecurityContextLogoutHandler])

  • Clear the SecurityContextRepository ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[SecurityContextLogoutHandler])

  • 清除所有 RememberMe authentication ( TokenRememberMeServices / PersistentTokenRememberMeServices )

  • 清除所有已保存的 CSRF token ( {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[CsrfLogoutHandler] )

  • Fire a LogoutSuccessEvent ({security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.html[LogoutSuccessEventPublishingLogoutHandler])

完成后,它将执行其默认 {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[LogoutSuccessHandler],它会重定向到 /login?logout

Customizing Logout URIs

由于 LogoutFilter`在 the filter chain中出现在 the `AuthorizationFilter之前,因此默认不必显式允许 `/logout`端点。因此,通常只有您自己创建的 custom logout endpoints才需要 `permitAll`配置才能访问。

例如,如果你只想更改 Spring Security 正在匹配的 URI,则可以通过以下方式在 logout DSL 中执行此操作:

Custom Logout Uri
  • Java

  • Kotlin

  • Xml

http
    .logout((logout) -> logout.logoutUrl("/my/logout/uri"))
http {
    logout {
        logoutUrl = "/my/logout/uri"
    }
}
<logout logout-url="/my/logout/uri"/>

并且不需要进行任何授权更改,因为它只是调整了 LogoutFilter

但是,如果您使用 {spring-framework-reference-url}web.html#spring-web[Spring MVC] 在自己的注销成功端点(或在极少数情况下,your own logout endpoint)中会话,那么您需要在 Spring Security 中允许它。这是因为 Spring MVC 在 Spring Security 处理完您的请求之后才会处理您的请求。

你可以像这样使用 authorizeHttpRequests<intercept-url> 来执行此操作:

Custom Logout Endpoint
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/my/success/endpoint").permitAll()
        // ...
    )
    .logout((logout) -> logout.logoutSuccessUrl("/my/success/endpoint"))
http {
    authorizeHttpRequests {
        authorize("/my/success/endpoint", permitAll)
    }
    logout {
        logoutSuccessUrl = "/my/success/endpoint"
    }
}
<http>
    <filter-url pattern="/my/success/endpoint" access="permitAll"/>
    <logout logout-success-url="/my/success/endpoint"/>
</http>

在此示例中,你告诉 LogoutFilter 完成时重定向到 /my/success/endpoint。而且,你明确允许 xref:servlet/authorization/authorize-http-requests.adoc[the AuthorizationFilter 中的 /my/success/endpoint 端点。

虽然多次指定它有点麻烦。如果你正在使用 Java 配置,你可以像这样在登出 DSL 中设置 permitAll 属性:

Permitting Custom Logout Endpoints
  • Java

  • Kotlin

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

这会为你添加所有登出 URI 到许可名单中。

Adding Clean-up Actions

如果你正在使用 Java 配置,你可以通过像这样在 logout DSL 中调用 addLogoutHandler 方法自己添加清除操作:

Custom Logout Handler
  • Java

  • Kotlin

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

由于 {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[LogoutHandler] 用于清理,因此它们不应抛出异常。

由于 {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[LogoutHandler] 是函数式接口,因此你可以将自定义的作为 Lambda 表达式提供。

某些登出处理程序配置很常见,它们直接在 logout DSL 和 <logout> 元素中公开。配置会话失效和需要删除哪些其他 cookie 就是一个例子。

例如,您可以将 {security-api-url}org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.html[CookieClearingLogoutHandler] 配置为上文所述。

或者,你可以像这样设置合适的配置值:

  • Java

  • Kotlin

  • Xml

http
    .logout((logout) -> logout.deleteCookies("our-custom-cookie"))
http {
    logout {
        deleteCookies = "our-custom-cookie"
    }
}
<http>
    <logout delete-cookies="our-custom-cookie"/>
</http>

指明 JSESSIONID cookie 没有必要,因为 {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[SecurityContextLogoutHandler] 会通过使会话无效来删除它。

Using Clear-Site-Data to Log Out the User

Clear-Site-Data HTTP 标头是一个浏览器能够支持的使用说明,用于清除归属于所有者网站的 cookie、存储和缓存。这是确保登出后清除所有内容(包括会话 cookie)的一种便捷且安全的方式。

你可以像这样配置 Spring Security 在登出后写入 Clear-Site-Data 标头:

Using Clear-Site-Data
  • Java

  • Kotlin

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

你给 ClearSiteDataHeaderWriter 构造函数想要清除的项目列表。

上述配置清除所有站点数据,但你也可以配置它仅清除 cookie,类似这样:

Using Clear-Site-Data to Clear Cookies
  • Java

  • Kotlin

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

Customizing Logout Success

虽然在大多数情况下使用 logoutSuccessUrl 就足够了,但您可能需要采取一些与注销完成后重定向到 URL 不同的操作。{security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[LogoutSuccessHandler] 是用于自定义注销成功操作的 Spring Security 组件。

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

Using Clear-Site-Data to Clear Cookies
  • Java

  • Kotlin

  • Xml

http
    .logout((logout) -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
http {
    logout {
        logoutSuccessHandler = HttpStatusReturningLogoutSuccessHandler()
    }
}
<bean name="mySuccessHandlerBean" class="org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler"/>
<http>
    <logout success-handler-ref="mySuccessHandlerBean"/>
</http>

由于 {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[LogoutSuccessHandler] 是一个函数式接口,您可以将自定义接口作为 lambda 提供。

Creating a Custom Logout Endpoint

强烈建议你使用提供的 logout DSL 来配置登出。一个原因是很容易忘记调用必需的 Spring Security 组件来确保正确且完整的登出。

事实上,直接使用 register a custom LogoutHandler 通常比创建 {spring-framework-reference-url}web.html#spring-web[Spring MVC] 端点来执行注销更简单。

也就是说,如果你发现自己处于需要自定义登出终结点的情况下,就像下面的示例:

Custom Logout Endpoint
  • Java

  • Kotlin

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

这样您将需要让该端点调用 Spring Security 的 {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[SecurityContextLogoutHandler] 以确保进行安全且完整的注销。至少需要类似以下内容:

Custom Logout Endpoint
  • Java

  • Kotlin

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";
}
val logoutHandler = SecurityContextLogoutHandler()

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

这将清除 {security-api-url}/org/springframework/security/core/context/SecurityContextHolderStrategy.html[SecurityContextHolderStrategy] 和 {security-api-url}/org/springframework/security/web/context/SecurityContextRepository.html[SecurityContextRepository](视需要而定)。

此外,你需要 explicitly permit the endpoint

未调用 {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[SecurityContextLogoutHandler] 意味着 the SecurityContext 仍然可以在后续请求中使用,即用户实际上并未退出。

Testing Logout

配置好注销后,您可以使用 Spring Security’s MockMvc support 来对其进行测试。

Further Logout-Related References