Handling Logouts
In an application where end users can login, they should also be able to 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:
-
I want to understand logout’s architecture
-
I want to know when I need to explicitly permit the
/logout
endpoint -
I want to clear cookies, storage, and/or cache when the user logs out
-
I am using OAuth 2.0 and I want to coordinate logout with an Authorization Server
-
I am using SAML 2.0 and I want to coordinate logout with an Identity Provider
-
I am using CAS and I want to coordinate logout with an Identity Provider
Understanding Logout’s Architecture
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
.
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
.
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.
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.
|
If you request POST /logout
, then it will perform the following default operations using a series of LogoutHandler
instances:
-
Invalidate the HTTP session (
SecurityContextLogoutHandler
) -
Clear the
SecurityContextHolderStrategy
(SecurityContextLogoutHandler
) -
Clear the
SecurityContextRepository
(SecurityContextLogoutHandler
) -
Clean up any RememberMe authentication (
TokenRememberMeServices
/PersistentTokenRememberMeServices
) -
Clear out any saved CSRF token (
CsrfLogoutHandler
) -
Fire a
LogoutSuccessEvent
(LogoutSuccessEventPublishingLogoutHandler
)
Once completed, then it will exercise its default LogoutSuccessHandler
which redirects to /login?logout
.
Customizing Logout URIs
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 custom logout endpoints that you create yourself generally require a permitAll
configuration to be reachable.
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:
-
Java
-
Kotlin
-
Xml
http
.logout((logout) -> logout.logoutUrl("/my/logout/uri"))
http {
logout {
logoutUrl = "/my/logout/uri"
}
}
<logout logout-url="/my/logout/uri"/>
and no authorization changes are necessary since it simply adjusts the LogoutFilter
.
However, if you stand up your own logout success endpoint (or in a rare case, your own logout endpoint), say using Spring MVC, you will need to permit it in Spring Security. This is because Spring MVC processes your request after Spring Security does.
You can do this using authorizeHttpRequests
or <intercept-url>
like so:
-
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>
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
.
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:
-
Java
-
Kotlin
http
.authorizeHttpRequests((authorize) -> authorize
// ...
)
.logout((logout) -> logout
.logoutSuccessUrl("/my/success/endpoint")
.permitAll()
)
http
authorizeHttpRequests {
// ...
}
logout {
logoutSuccessUrl = "/my/success/endpoint"
permitAll = true
}
which will add all logout URIs to the permit list for you.
Adding Clean-up Actions
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:
-
Java
-
Kotlin
CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
http
.logout((logout) -> logout.addLogoutHandler(cookies))
http {
logout {
addLogoutHandler(CookieClearingLogoutHandler("our-custom-cookie"))
}
}
Because LogoutHandler instances are for the purposes of cleanup, they should not throw exceptions.
|
Since LogoutHandler is a functional interface, you can provide a custom one as a lambda.
|
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.
For example, you can configure the CookieClearingLogoutHandler
as seen above.
-
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>
Specifying that the JSESSIONID cookie is not necessary since SecurityContextLogoutHandler removes it by virtue of invalidating the session.
|
Using Clear-Site-Data to Log Out the User
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.
You can add configure Spring Security to write the Clear-Site-Data
header on logout like so:
-
Java
-
Kotlin
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter())
http {
logout {
addLogoutHandler(clearSiteData)
}
}
You give the ClearSiteDataHeaderWriter
constructor the list of things that you want to be cleared out.
The above configuration clears out all site data, but you can also configure it to remove just cookies like so:
-
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
While using logoutSuccessUrl
will suffice for most cases, you may need to do something different from redirecting to a URL once logout is complete.
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:
-
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>
Since LogoutSuccessHandler is a functional interface, you can provide a custom one as a lambda.
|
Creating a Custom Logout Endpoint
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.
In fact, it is often simpler to register a custom LogoutHandler
than create a 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:
-
Java
-
Kotlin
@PostMapping("/my/logout")
public String performLogout() {
// .. perform logout
return "redirect:/home";
}
@PostMapping("/my/logout")
fun performLogout(): String {
// .. perform logout
return "redirect:/home"
}
then you will need to have that endpoint invoke Spring Security’s SecurityContextLogoutHandler
to ensure a secure and complete logout.
Something like the following is needed at a minimum:
-
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"
}
Such will clear out the SecurityContextHolderStrategy
and SecurityContextRepository
as needed.
Also, you’ll need to explicitly permit the endpoint.
Failing to call SecurityContextLogoutHandler means that the SecurityContext could still be available on subsequent requests, meaning that the user is not actually logged out.
|
Testing Logout
Once you have logout configured you can test it using Spring Security’s MockMvc support.
Further Logout-Related References
-
Logging Out in section CSRF Caveats
-
Section Single Logout (CAS protocol)
-
Documentation for the logout element in the Spring Security XML Namespace section