logout.adoc 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. = OIDC Logout
  2. Once an end user is able to login to your application, it's important to consider how they will log out.
  3. Generally speaking, there are three use cases for you to consider:
  4. 1. I want to perform only a local logout
  5. 2. I want to log out both my application and the OIDC Provider, initiated by my application
  6. 3. I want to log out both my application and the OIDC Provider, initiated by the OIDC Provider
  7. [[configure-local-logout]]
  8. == Local Logout
  9. To perform a local logout, no special OIDC configuration is needed.
  10. Spring Security automatically stands up a local logout endpoint, which you can xref:reactive/authentication/logout.adoc[configure through the `logout()` DSL].
  11. [[configure-client-initiated-oidc-logout]]
  12. [[oauth2login-advanced-oidc-logout]]
  13. == OpenID Connect 1.0 Client-Initiated Logout
  14. OpenID Connect Session Management 1.0 allows the ability to log out the end user at the Provider by using the Client.
  15. One of the strategies available is https://openid.net/specs/openid-connect-rpinitiated-1_0.html[RP-Initiated Logout].
  16. If the OpenID Provider supports both Session Management and https://openid.net/specs/openid-connect-discovery-1_0.html[Discovery], the client can obtain the `end_session_endpoint` `URL` from the OpenID Provider's https://openid.net/specs/openid-connect-session-1_0.html#OPMetadata[Discovery Metadata].
  17. You can do so by configuring the `ClientRegistration` with the `issuer-uri`, as follows:
  18. [source,yaml]
  19. ----
  20. spring:
  21. security:
  22. oauth2:
  23. client:
  24. registration:
  25. okta:
  26. client-id: okta-client-id
  27. client-secret: okta-client-secret
  28. ...
  29. provider:
  30. okta:
  31. issuer-uri: https://dev-1234.oktapreview.com
  32. ----
  33. Also, you should configure `OidcClientInitiatedServerLogoutSuccessHandler`, which implements RP-Initiated Logout, as follows:
  34. [tabs]
  35. ======
  36. Java::
  37. +
  38. [source,java,role="primary"]
  39. ----
  40. @Configuration
  41. @EnableWebFluxSecurity
  42. public class OAuth2LoginSecurityConfig {
  43. @Autowired
  44. private ReactiveClientRegistrationRepository clientRegistrationRepository;
  45. @Bean
  46. public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception {
  47. http
  48. .authorizeExchange((authorize) -> authorize
  49. .anyExchange().authenticated()
  50. )
  51. .oauth2Login(withDefaults())
  52. .logout((logout) -> logout
  53. .logoutSuccessHandler(oidcLogoutSuccessHandler())
  54. );
  55. return http.build();
  56. }
  57. private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
  58. OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
  59. new OidcClientInitiatedServerLogoutSuccessHandler(this.clientRegistrationRepository);
  60. // Sets the location that the End-User's User Agent will be redirected to
  61. // after the logout has been performed at the Provider
  62. oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
  63. return oidcLogoutSuccessHandler;
  64. }
  65. }
  66. ----
  67. Kotlin::
  68. +
  69. [source,kotlin,role="secondary"]
  70. ----
  71. @Configuration
  72. @EnableWebFluxSecurity
  73. class OAuth2LoginSecurityConfig {
  74. @Autowired
  75. private lateinit var clientRegistrationRepository: ReactiveClientRegistrationRepository
  76. @Bean
  77. open fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  78. http {
  79. authorizeExchange {
  80. authorize(anyExchange, authenticated)
  81. }
  82. oauth2Login { }
  83. logout {
  84. logoutSuccessHandler = oidcLogoutSuccessHandler()
  85. }
  86. }
  87. return http.build()
  88. }
  89. private fun oidcLogoutSuccessHandler(): ServerLogoutSuccessHandler {
  90. val oidcLogoutSuccessHandler = OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)
  91. // Sets the location that the End-User's User Agent will be redirected to
  92. // after the logout has been performed at the Provider
  93. oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
  94. return oidcLogoutSuccessHandler
  95. }
  96. }
  97. ----
  98. ======
  99. [NOTE]
  100. ====
  101. `OidcClientInitiatedServerLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
  102. If used, the application's base URL, such as `https://app.example.org`, replaces it at request time.
  103. ====
  104. [[configure-provider-initiated-oidc-logout]]
  105. == OpenID Connect 1.0 Back-Channel Logout
  106. OpenID Connect Session Management 1.0 allows the ability to log out the end user at the Client by having the Provider make an API call to the Client.
  107. This is referred to as https://openid.net/specs/openid-connect-backchannel-1_0.html[OIDC Back-Channel Logout].
  108. To enable this, you can stand up the Back-Channel Logout endpoint in the DSL like so:
  109. [tabs]
  110. ======
  111. Java::
  112. +
  113. [source,java,role="primary"]
  114. ----
  115. @Bean
  116. public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception {
  117. http
  118. .authorizeExchange((authorize) -> authorize
  119. .anyExchange().authenticated()
  120. )
  121. .oauth2Login(withDefaults())
  122. .oidcLogout((logout) -> logout
  123. .backChannel(Customizer.withDefaults())
  124. );
  125. return http.build();
  126. }
  127. ----
  128. Kotlin::
  129. +
  130. [source,kotlin,role="secondary"]
  131. ----
  132. @Bean
  133. open fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  134. http {
  135. authorizeExchange {
  136. authorize(anyExchange, authenticated)
  137. }
  138. oauth2Login { }
  139. oidcLogout {
  140. backChannel { }
  141. }
  142. }
  143. return http.build()
  144. }
  145. ----
  146. ======
  147. And that's it!
  148. This will stand up the endpoint `+/logout/connect/back-channel/{registrationId}+` which the OIDC Provider can request to invalidate a given session of an end user in your application.
  149. [NOTE]
  150. `oidcLogout` requires that `oauth2Login` also be configured.
  151. [NOTE]
  152. `oidcLogout` requires that the session cookie be called `JSESSIONID` in order to correctly log out each session through a backchannel.
  153. === Back-Channel Logout Architecture
  154. Consider a `ClientRegistration` whose identifier is `registrationId`.
  155. The overall flow for a Back-Channel logout is like this:
  156. 1. At login time, Spring Security correlates the ID Token, CSRF Token, and Provider Session ID (if any) to your application's session id in its `ReactiveOidcSessionStrategy` implementation.
  157. 2. Then at logout time, your OIDC Provider makes an API call to `/logout/connect/back-channel/registrationId` including a Logout Token that indicates either the `sub` (the End User) or the `sid` (the Provider Session ID) to logout.
  158. 3. Spring Security validates the token's signature and claims.
  159. 4. If the token contains a `sid` claim, then only the Client's session that correlates to that provider session is terminated.
  160. 5. Otherwise, if the token contains a `sub` claim, then all that Client's sessions for that End User are terminated.
  161. [NOTE]
  162. Remember that Spring Security's OIDC support is multi-tenant.
  163. This means that it will only terminate sessions whose Client matches the `aud` claim in the Logout Token.
  164. === Customizing the OIDC Provider Session Strategy
  165. By default, Spring Security stores in-memory all links between the OIDC Provider session and the Client session.
  166. There are a number of circumstances, like a clustered application, where it would be nice to store this instead in a separate location, like a database.
  167. You can achieve this by configuring a custom `ReactiveOidcSessionStrategy`, like so:
  168. [tabs]
  169. ======
  170. Java::
  171. +
  172. [source,java,role="primary"]
  173. ----
  174. @Component
  175. public final class MySpringDataOidcSessionStrategy implements OidcSessionStrategy {
  176. private final OidcProviderSessionRepository sessions;
  177. // ...
  178. @Override
  179. public void saveSessionInformation(OidcSessionInformation info) {
  180. this.sessions.save(info);
  181. }
  182. @Override
  183. public OidcSessionInformation(String clientSessionId) {
  184. return this.sessions.removeByClientSessionId(clientSessionId);
  185. }
  186. @Override
  187. public Iterable<OidcSessionInformation> removeSessionInformation(OidcLogoutToken token) {
  188. return token.getSessionId() != null ?
  189. this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
  190. this.sessions.removeBySubjectAndIssuerAndAudience(...);
  191. }
  192. }
  193. ----
  194. Kotlin::
  195. +
  196. [source,kotlin,role="secondary"]
  197. ----
  198. @Component
  199. class MySpringDataOidcSessionStrategy: ReactiveOidcSessionStrategy {
  200. val sessions: OidcProviderSessionRepository
  201. // ...
  202. @Override
  203. fun saveSessionInformation(info: OidcSessionInformation): Mono<Void> {
  204. return this.sessions.save(info)
  205. }
  206. @Override
  207. fun removeSessionInformation(clientSessionId: String): Mono<OidcSessionInformation> {
  208. return this.sessions.removeByClientSessionId(clientSessionId);
  209. }
  210. @Override
  211. fun removeSessionInformation(token: OidcLogoutToken): Flux<OidcSessionInformation> {
  212. return token.getSessionId() != null ?
  213. this.sessions.removeBySessionIdAndIssuerAndAudience(...) :
  214. this.sessions.removeBySubjectAndIssuerAndAudience(...);
  215. }
  216. }
  217. ----
  218. ======