Handling Logouts

在最终用户可以 login 的应用程序中,他们还应该能够注销。

In an application where end users can login, they should also be able to logout.

默认情况下,Spring Security 建立一个 /logout 端点,因此不需要其他代码。

By default, Spring Security stands up a /logout endpoint, so no additional code is necessary.

本节的其余部分涵盖了许多你可能需要考虑的使用案例:

The rest of this section covers a number of use cases for you to consider:

Understanding Logout’s Architecture

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

When you include the spring-boot-starter-security dependency or use the @EnableWebSecurity annotation, Spring Security will add its logout support and by default respond both to GET /logout and POST /logout.

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

If you request GET /logout, then Spring Security displays a logout confirmation page. Aside from providing a valuable double-checking mechanism for the user, it also provides a simple way to provide the needed CSRF token to POST /logout.

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

Please note that if CSRF protection is disabled in configuration, no logout confirmation page is shown to the user and the logout is performed directly.

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

In your application it is not necessary to use GET /logout to perform a logout. So long as the needed CSRF token is present in the request, your application can simply POST /logout to induce a logout.

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

If you request POST /logout, then it will perform the following default operations using a series of {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[LogoutHandler]s:

  • Invalidate the HTTP session ({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])

  • Clean up any RememberMe authentication (TokenRememberMeServices / PersistentTokenRememberMeServices)

  • Clear out any saved 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

Once completed, then it will exercise its default {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[LogoutSuccessHandler] which redirects to /login?logout.

Customizing Logout URIs

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

Since the LogoutFilter appears before the AuthorizationFilter in the filter chain, it is not necessary by default to explicitly permit the /logout endpoint. Thus, only permit-logout-endpoints that you create yourself generally require a permitAll configuration to be reachable.

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

For example, if you want to simply change the URI that Spring Security is matching, you can do so in the logout DSL in following way:

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

and no authorization changes are necessary since it simply adjusts the LogoutFilter.

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

However, if you stand up your own logout success endpoint (or in a rare case, creating-custom-logout-endpoint), say using {spring-framework-reference-url}web.html#spring-web[Spring MVC], you will need to permit it in Spring Security. This is because Spring MVC processes your request after Spring Security does.

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

You can do this using authorizeHttpRequests or <intercept-url> like so:

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 端点。

In this example, you tell the LogoutFilter to redirect to /my/success/endpoint when it is done. And, you explicitly permit the /my/success/endpoint endpoint in the AuthorizationFilter.

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

Specifying it twice can be cumbersome, though. If you are using Java configuration, you can instead set the permitAll property in the logout DSL like so:

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 到许可名单中。

which will add all logout URIs to the permit list for you.

Adding Clean-up Actions

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

If you are using Java configuration, you can add clean up actions of your own by calling the addLogoutHandler method in the logout DSL, like so:

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] 用于清理,因此它们不应抛出异常。

Because {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[LogoutHandler]s are for the purposes of cleanup, they should not throw exceptions.

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

Since {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[LogoutHandler] is a functional interface, you can provide a custom one as a lambda.

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

Some logout handler configurations are common enough that they are exposed directly in the logout DSL and <logout> element. One example is configuring session invalidation and another is which additional cookies should be deleted.

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

For example, you can configure the {security-api-url}org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.html[CookieClearingLogoutHandler] as seen above.

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

Or you can instead set the appropriate configuration value like so:

  • 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] 会通过使会话无效来删除它。

Specifying that the JSESSIONID cookie is not necessary since {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[SecurityContextLogoutHandler] removes it by virtue of invalidating the session.

Using Clear-Site-Data to Log Out the User

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

The Clear-Site-Data HTTP header is one that browsers support as an instruction to clear cookies, storage, and cache that belong to the owning website. This is a handy and secure way to ensure that everything, including the session cookie, is cleaned up on logout.

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

You can add configure Spring Security to write the Clear-Site-Data header on logout like so:

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 构造函数想要清除的项目列表。

You give the ClearSiteDataHeaderWriter constructor the list of things that you want to be cleared out.

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

The above configuration clears out all site data, but you can also configure it to remove just cookies like so:

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 组件。

While using logoutSuccessUrl will suffice for most cases, you may need to do something different from redirecting to a URL once logout is complete. {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[LogoutSuccessHandler] is the Spring Security component for customizing logout success actions.

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

For example, instead of redirecting, you may want to only return a status code. In this case, you can provide a success handler instance, like so:

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 提供。

Since {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[LogoutSuccessHandler] is a functional interface, you can provide a custom one as a lambda.

Creating a Custom Logout Endpoint

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

It is strongly recommended that you use the provided logout DSL to configure logout. One reason is that its easy to forget to call the needed Spring Security components to ensure a proper and complete logout.

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

In fact, it is often simpler to add-logout-handler than create a {spring-framework-reference-url}web.html#spring-web[Spring MVC] endpoint for performing logout.

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

That said, if you find yourself in a circumstance where a custom logout endpoint is needed, like the following one:

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] 以确保进行安全且完整的注销。至少需要类似以下内容:

then you will need to have that endpoint invoke Spring Security’s {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[SecurityContextLogoutHandler] to ensure a secure and complete logout. Something like the following is needed at a minimum:

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](视需要而定)。

Such will clear out the {security-api-url}/org/springframework/security/core/context/SecurityContextHolderStrategy.html[SecurityContextHolderStrategy] and {security-api-url}/org/springframework/security/web/context/SecurityContextRepository.html[SecurityContextRepository] as needed.

此外,你需要 explicitly permit the endpoint

Also, you’ll need to permit-logout-endpoints.

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

Failing to call {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[SecurityContextLogoutHandler] means that the SecurityContext could still be available on subsequent requests, meaning that the user is not actually logged out.

Testing Logout

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

Once you have logout configured you can test it using Spring Security’s MockMvc support.

Further Logout-Related References