logout.adoc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. [[jc-logout]]
  2. = Handling Logouts
  3. In an application where end users can xref:servlet/authentication/index.adoc[login], they should also be able to logout.
  4. By default, Spring Security stands up a `/logout` endpoint, so no additional code is necessary.
  5. The rest of this section covers a number of use cases for you to consider:
  6. * I want to <<logout-java-configuration,understand logout's architecture>>
  7. * I want to <<customizing-logout-uris, customize the logout or logout success URI>>
  8. * I want to know when I need to <<permit-logout-endpoints, explicitly permit the `/logout` endpoint>>
  9. * I want to <<clear-all-site-data, clear cookies, storage, and/or cache>> when the user logs out
  10. * I am using OAuth 2.0 and I want to xref:servlet/oauth2/login/advanced.adoc#oauth2login-advanced-oidc-logout[coordinate logout with an Authorization Server]
  11. * I am using SAML 2.0 and I want to xref:servlet/saml2/logout.adoc[coordinate logout with an Identity Provider]
  12. * I am using CAS and I want to xref:servlet/authentication/cas.adoc#cas-singlelogout[coordinate logout with an Identity Provider]
  13. [[logout-architecture]]
  14. [[logout-java-configuration]]
  15. == Understanding Logout's Architecture
  16. When you include {spring-boot-reference-url}using.html#using.build-systems.starters[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`.
  17. If you request `GET /logout`, then Spring Security displays a logout confirmation page.
  18. Aside from providing a valuable double-checking mechanism for the user, it also provides a simple way to provide xref:servlet/exploits/csrf.adoc[the needed CSRF token] to `POST /logout`.
  19. Please note that if xref:servlet/exploits/csrf.adoc[CSRF protection] is disabled in configuration, no logout confirmation page is shown to the user and the logout is performed directly.
  20. [TIP]
  21. In your application it is not necessary to use `GET /logout` to perform a logout.
  22. So long as xref:servlet/exploits/csrf.adoc[the needed CSRF token] is present in the request, your application can simply `POST /logout` to induce a logout.
  23. If you request `POST /logout`, then it will perform the following default operations using a series of javadoc:org.springframework.security.web.authentication.logout.LogoutHandler[] instances:
  24. - Invalidate the HTTP session (javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[])
  25. - Clear the xref:servlet/authentication/session-management.adoc#use-securitycontextholderstrategy[`SecurityContextHolderStrategy`] (javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[])
  26. - Clear the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] (javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[])
  27. - Clean up any xref:servlet/authentication/rememberme.adoc[RememberMe authentication] (`TokenRememberMeServices` / `PersistentTokenRememberMeServices`)
  28. - Clear out any saved xref:servlet/exploits/csrf.adoc[CSRF token] (javadoc:org.springframework.security.web.csrf.CsrfLogoutHandler[])
  29. - xref:servlet/authentication/events.adoc[Fire] a `LogoutSuccessEvent` (javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler[])
  30. Once completed, then it will exercise its default javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessHandler[] which redirects to `/login?logout`.
  31. [[customizing-logout-uris]]
  32. == Customizing Logout URIs
  33. Since the `LogoutFilter` appears before xref:servlet/authorization/authorize-http-requests.adoc[the `AuthorizationFilter`] in xref:servlet/architecture.adoc#servlet-filterchain-figure[the filter chain], it is not necessary by default to explicitly permit the `/logout` endpoint.
  34. Thus, only <<permit-logout-endpoints,custom logout endpoints>> that you create yourself generally require a `permitAll` configuration to be reachable.
  35. 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:
  36. .Custom Logout Uri
  37. [tabs]
  38. ======
  39. Java::
  40. +
  41. [source,java,role="primary"]
  42. ----
  43. http
  44. .logout((logout) -> logout.logoutUrl("/my/logout/uri"))
  45. ----
  46. Kotlin::
  47. +
  48. [source,kotlin,role="secondary"]
  49. ----
  50. http {
  51. logout {
  52. logoutUrl = "/my/logout/uri"
  53. }
  54. }
  55. ----
  56. Xml::
  57. +
  58. [source,xml,role="secondary"]
  59. ----
  60. <logout logout-url="/my/logout/uri"/>
  61. ----
  62. ======
  63. and no authorization changes are necessary since it simply adjusts the `LogoutFilter`.
  64. [[permit-logout-endpoints]]
  65. However, if you stand up your own logout success endpoint (or in a rare case, <<creating-custom-logout-endpoint, your own logout endpoint>>), say using {spring-framework-reference-url}web.html#spring-web[Spring MVC], you will need to permit it in Spring Security.
  66. This is because Spring MVC processes your request after Spring Security does.
  67. You can do this using `authorizeHttpRequests` or `<intercept-url>` like so:
  68. .Custom Logout Endpoint
  69. [tabs]
  70. ======
  71. Java::
  72. +
  73. [source,java,role="primary"]
  74. ----
  75. http
  76. .authorizeHttpRequests((authorize) -> authorize
  77. .requestMatchers("/my/success/endpoint").permitAll()
  78. // ...
  79. )
  80. .logout((logout) -> logout.logoutSuccessUrl("/my/success/endpoint"))
  81. ----
  82. Kotlin::
  83. +
  84. [source,kotlin,role="secondary"]
  85. ----
  86. http {
  87. authorizeHttpRequests {
  88. authorize("/my/success/endpoint", permitAll)
  89. }
  90. logout {
  91. logoutSuccessUrl = "/my/success/endpoint"
  92. }
  93. }
  94. ----
  95. Xml::
  96. +
  97. [source,xml,role="secondary"]
  98. ----
  99. <http>
  100. <filter-url pattern="/my/success/endpoint" access="permitAll"/>
  101. <logout logout-success-url="/my/success/endpoint"/>
  102. </http>
  103. ----
  104. ======
  105. In this example, you tell the `LogoutFilter` to redirect to `/my/success/endpoint` when it is done.
  106. And, you explicitly permit the `/my/success/endpoint` endpoint in xref:servlet/authorization/authorize-http-requests.adoc[the `AuthorizationFilter`].
  107. Specifying it twice can be cumbersome, though.
  108. If you are using Java configuration, you can instead set the `permitAll` property in the logout DSL like so:
  109. .Permitting Custom Logout Endpoints
  110. [tabs]
  111. ======
  112. Java::
  113. +
  114. [source,java,role="primary"]
  115. ----
  116. http
  117. .authorizeHttpRequests((authorize) -> authorize
  118. // ...
  119. )
  120. .logout((logout) -> logout
  121. .logoutSuccessUrl("/my/success/endpoint")
  122. .permitAll()
  123. )
  124. ----
  125. Kotlin::
  126. +
  127. [source,kotlin,role="secondary"]
  128. ----
  129. http
  130. authorizeHttpRequests {
  131. // ...
  132. }
  133. logout {
  134. logoutSuccessUrl = "/my/success/endpoint"
  135. permitAll = true
  136. }
  137. ----
  138. ======
  139. which will add all logout URIs to the permit list for you.
  140. [[add-logout-handler]]
  141. == Adding Clean-up Actions
  142. 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:
  143. .Custom Logout Handler
  144. [tabs]
  145. ======
  146. Java::
  147. +
  148. [source,java,role="primary"]
  149. ----
  150. CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
  151. http
  152. .logout((logout) -> logout.addLogoutHandler(cookies))
  153. ----
  154. Kotlin::
  155. +
  156. [source,kotlin,role="secondary"]
  157. ----
  158. http {
  159. logout {
  160. addLogoutHandler(CookieClearingLogoutHandler("our-custom-cookie"))
  161. }
  162. }
  163. ----
  164. ======
  165. [NOTE]
  166. Because javadoc:org.springframework.security.web.authentication.logout.LogoutHandler[] instances are for the purposes of cleanup, they should not throw exceptions.
  167. [TIP]
  168. Since javadoc:org.springframework.security.web.authentication.logout.LogoutHandler[] is a functional interface, you can provide a custom one as a lambda.
  169. Some logout handler configurations are common enough that they are exposed directly in the `logout` DSL and `<logout>` element.
  170. One example is configuring session invalidation and another is which additional cookies should be deleted.
  171. For example, you can configure the javadoc:org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler[] as seen above.
  172. [[delete-cookies]]
  173. Or you can instead set the appropriate configuration value like so:
  174. [tabs]
  175. ======
  176. Java::
  177. +
  178. [source,java,role="primary"]
  179. ----
  180. http
  181. .logout((logout) -> logout.deleteCookies("our-custom-cookie"))
  182. ----
  183. Kotlin::
  184. +
  185. [source,kotlin,role="secondary"]
  186. ----
  187. http {
  188. logout {
  189. deleteCookies = "our-custom-cookie"
  190. }
  191. }
  192. ----
  193. Xml::
  194. +
  195. [source,kotlin,role="secondary"]
  196. ----
  197. <http>
  198. <logout delete-cookies="our-custom-cookie"/>
  199. </http>
  200. ----
  201. ======
  202. [NOTE]
  203. Specifying that the `JSESSIONID` cookie is not necessary since javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[] removes it by virtue of invalidating the session.
  204. [[clear-all-site-data]]
  205. === Using Clear-Site-Data to Log Out the User
  206. 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.
  207. This is a handy and secure way to ensure that everything, including the session cookie, is cleaned up on logout.
  208. You can add configure Spring Security to write the `Clear-Site-Data` header on logout like so:
  209. .Using Clear-Site-Data
  210. [tabs]
  211. ======
  212. Java::
  213. +
  214. [source,java,role="primary"]
  215. ----
  216. HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
  217. http
  218. .logout((logout) -> logout.addLogoutHandler(clearSiteData))
  219. ----
  220. Kotlin::
  221. +
  222. [source,kotlin,role="secondary"]
  223. ----
  224. val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter())
  225. http {
  226. logout {
  227. addLogoutHandler(clearSiteData)
  228. }
  229. }
  230. ----
  231. ======
  232. You give the `ClearSiteDataHeaderWriter` constructor the list of things that you want to be cleared out.
  233. The above configuration clears out all site data, but you can also configure it to remove just cookies like so:
  234. .Using Clear-Site-Data to Clear Cookies
  235. [tabs]
  236. ======
  237. Java::
  238. +
  239. [source,java,role="primary"]
  240. ----
  241. HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.COOKIES));
  242. http
  243. .logout((logout) -> logout.addLogoutHandler(clearSiteData))
  244. ----
  245. Kotlin::
  246. +
  247. [source,kotlin,role="secondary"]
  248. ----
  249. val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directive.COOKIES))
  250. http {
  251. logout {
  252. addLogoutHandler(clearSiteData)
  253. }
  254. }
  255. ----
  256. ======
  257. [[customizing-logout-success]]
  258. == Customizing Logout Success
  259. While using `logoutSuccessUrl` will suffice for most cases, you may need to do something different from redirecting to a URL once logout is complete.
  260. javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessHandler[] is the Spring Security component for customizing logout success actions.
  261. For example, instead of redirecting, you may want to only return a status code.
  262. In this case, you can provide a success handler instance, like so:
  263. .Using Clear-Site-Data to Clear Cookies
  264. [tabs]
  265. ======
  266. Java::
  267. +
  268. [source,java,role="primary"]
  269. ----
  270. http
  271. .logout((logout) -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
  272. ----
  273. Kotlin::
  274. +
  275. [source,kotlin,role="secondary"]
  276. ----
  277. http {
  278. logout {
  279. logoutSuccessHandler = HttpStatusReturningLogoutSuccessHandler()
  280. }
  281. }
  282. ----
  283. Xml::
  284. +
  285. [source,xml,role="secondary"]
  286. ----
  287. <bean name="mySuccessHandlerBean" class="org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler"/>
  288. <http>
  289. <logout success-handler-ref="mySuccessHandlerBean"/>
  290. </http>
  291. ----
  292. ======
  293. [TIP]
  294. Since javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessHandler[] is a functional interface, you can provide a custom one as a lambda.
  295. [[creating-custom-logout-endpoint]]
  296. == Creating a Custom Logout Endpoint
  297. It is strongly recommended that you use the provided `logout` DSL to configure logout.
  298. One reason is that its easy to forget to call the needed Spring Security components to ensure a proper and complete logout.
  299. In fact, it is often simpler to <<add-logout-handler, register a custom `LogoutHandler`>> than create a {spring-framework-reference-url}web.html#spring-web[Spring MVC] endpoint for performing logout.
  300. That said, if you find yourself in a circumstance where a custom logout endpoint is needed, like the following one:
  301. .Custom Logout Endpoint
  302. [tabs]
  303. ======
  304. Java::
  305. +
  306. [source,java,role="primary"]
  307. ----
  308. @PostMapping("/my/logout")
  309. public String performLogout() {
  310. // .. perform logout
  311. return "redirect:/home";
  312. }
  313. ----
  314. Kotlin::
  315. +
  316. [source,kotlin,role="secondary"]
  317. ----
  318. @PostMapping("/my/logout")
  319. fun performLogout(): String {
  320. // .. perform logout
  321. return "redirect:/home"
  322. }
  323. ----
  324. ======
  325. then you will need to have that endpoint invoke Spring Security's javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[] to ensure a secure and complete logout.
  326. Something like the following is needed at a minimum:
  327. .Custom Logout Endpoint
  328. [tabs]
  329. ======
  330. Java::
  331. +
  332. [source,java,role="primary"]
  333. ----
  334. SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
  335. @PostMapping("/my/logout")
  336. public String performLogout(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
  337. // .. perform logout
  338. this.logoutHandler.doLogout(request, response, authentication);
  339. return "redirect:/home";
  340. }
  341. ----
  342. Kotlin::
  343. +
  344. [source,kotlin,role="secondary"]
  345. ----
  346. val logoutHandler = SecurityContextLogoutHandler()
  347. @PostMapping("/my/logout")
  348. fun performLogout(val authentication: Authentication, val request: HttpServletRequest, val response: HttpServletResponse): String {
  349. // .. perform logout
  350. this.logoutHandler.doLogout(request, response, authentication)
  351. return "redirect:/home"
  352. }
  353. ----
  354. ======
  355. Such will clear out the javadoc:org.springframework.security.core.context.SecurityContextHolderStrategy[] and javadoc:org.springframework.security.web.context.SecurityContextRepository[] as needed.
  356. Also, you'll need to <<permit-logout-endpoints, explicitly permit the endpoint>>.
  357. [WARNING]
  358. Failing to call javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[] means that xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[the `SecurityContext`] could still be available on subsequent requests, meaning that the user is not actually logged out.
  359. [[testing-logout]]
  360. == Testing Logout
  361. Once you have logout configured you can test it using xref:servlet/test/mockmvc/logout.adoc[Spring Security's MockMvc support].
  362. [[jc-logout-references]]
  363. == Further Logout-Related References
  364. - xref:servlet/test/mockmvc/logout.adoc#test-logout[Testing Logout]
  365. - xref:servlet/integrations/servlet-api.adoc#servletapi-logout[HttpServletRequest.logout()]
  366. - xref:servlet/authentication/rememberme.adoc#remember-me-impls[Remember-Me Interfaces and Implementations]
  367. - xref:servlet/exploits/csrf.adoc#csrf-considerations-logout[Logging Out] in section CSRF Caveats
  368. - Section xref:servlet/authentication/cas.adoc#cas-singlelogout[Single Logout] (CAS protocol)
  369. - Documentation for the xref:servlet/appendix/namespace/http.adoc#nsa-logout[logout element] in the Spring Security XML Namespace section