logout.adoc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. [[servlet-saml2login-logout]]
  2. = Performing Single Logout
  3. Spring Security ships with support for RP- and AP-initiated SAML 2.0 Single Logout.
  4. Briefly, there are two use cases Spring Security supports:
  5. * **RP-Initiated** - Your application has an endpoint that, when POSTed to, will logout the user and send a `saml2:LogoutRequest` to the asserting party.
  6. Thereafter, the asserting party will send back a `saml2:LogoutResponse` and allow your application to respond
  7. * **AP-Initiated** - Your application has an endpoint that will receive a `saml2:LogoutRequest` from the asserting party.
  8. Your application will complete its logout at that point and then send a `saml2:LogoutResponse` to the asserting party.
  9. [NOTE]
  10. In the **AP-Initiated** scenario, any local redirection that your application would do post-logout is rendered moot.
  11. Once your application sends a `saml2:LogoutResponse`, it no longer has control of the browser.
  12. == Minimal Configuration for Single Logout
  13. To use Spring Security's SAML 2.0 Single Logout feature, you will need the following things:
  14. * First, the asserting party must support SAML 2.0 Single Logout
  15. * Second, the asserting party should be configured to sign and POST `saml2:LogoutRequest` s and `saml2:LogoutResponse` s your application's `/logout/saml2/slo` endpoint
  16. * Third, your application must have a PKCS#8 private key and X.509 certificate for signing `saml2:LogoutRequest` s and `saml2:LogoutResponse` s
  17. You can begin from the initial minimal example and add the following configuration:
  18. [source,java]
  19. ----
  20. @Value("${private.key}") RSAPrivateKey key;
  21. @Value("${public.certificate}") X509Certificate certificate;
  22. @Bean
  23. RelyingPartyRegistrationRepository registrations() {
  24. Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate);
  25. RelyingPartyRegistration registration = RelyingPartyRegistrations
  26. .fromMetadataLocation("https://ap.example.org/metadata")
  27. .registrationId("id")
  28. .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo")
  29. .signingX509Credentials((signing) -> signing.add(credential)) <1>
  30. .build();
  31. return new InMemoryRelyingPartyRegistrationRepository(registration);
  32. }
  33. @Bean
  34. SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception {
  35. http
  36. .authorizeHttpRequests((authorize) -> authorize
  37. .anyRequest().authenticated()
  38. )
  39. .saml2Login(withDefaults())
  40. .saml2Logout(withDefaults()); <2>
  41. return http.build();
  42. }
  43. ----
  44. <1> - First, add your signing key to the `RelyingPartyRegistration` instance or to xref:servlet/saml2/login/overview.adoc#servlet-saml2login-rpr-duplicated[multiple instances]
  45. <2> - Second, indicate that your application wants to use SAML SLO to logout the end user
  46. === Runtime Expectations
  47. Given the above configuration any logged in user can send a `POST /logout` to your application to perform RP-initiated SLO.
  48. Your application will then do the following:
  49. 1. Logout the user and invalidate the session
  50. 2. Use a `Saml2LogoutRequestResolver` to create, sign, and serialize a `<saml2:LogoutRequest>` based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] associated with the currently logged-in user.
  51. 3. Send a redirect or post to the asserting party based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`]
  52. 4. Deserialize, verify, and process the `<saml2:LogoutResponse>` sent by the asserting party
  53. 5. Redirect to any configured successful logout endpoint
  54. Also, your application can participate in an AP-initiated logout when the asserting party sends a `<saml2:LogoutRequest>` to `/logout/saml2/slo`:
  55. 1. Use a `Saml2LogoutRequestHandler` to deserialize, verify, and process the `<saml2:LogoutRequest>` sent by the asserting party
  56. 2. Logout the user and invalidate the session
  57. 3. Create, sign, and serialize a `<saml2:LogoutResponse>` based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] associated with the just logged-out user
  58. 4. Send a redirect or post to the asserting party based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`]
  59. NOTE: Adding `saml2Logout` adds the capability for logout to the service provider.
  60. Because it is an optional capability, you need to enable it for each individual `RelyingPartyRegistration`.
  61. You can do this by setting the `RelyingPartyRegistration.Builder#singleLogoutServiceLocation` property.
  62. == Configuring Logout Endpoints
  63. There are three behaviors that can be triggered by different endpoints:
  64. * RP-initiated logout, which allows an authenticated user to `POST` and trigger the logout process by sending the asserting party a `<saml2:LogoutRequest>`
  65. * AP-initiated logout, which allows an asserting party to send a `<saml2:LogoutRequest>` to the application
  66. * AP logout response, which allows an asserting party to send a `<saml2:LogoutResponse>` in response to the RP-initiated `<saml2:LogoutRequest>`
  67. The first is triggered by performing normal `POST /logout` when the principal is of type `Saml2AuthenticatedPrincipal`.
  68. The second is triggered by POSTing to the `/logout/saml2/slo` endpoint with a `SAMLRequest` signed by the asserting party.
  69. The third is triggered by POSTing to the `/logout/saml2/slo` endpoint with a `SAMLResponse` signed by the asserting party.
  70. Because the user is already logged in or the original Logout Request is known, the `registrationId` is already known.
  71. For this reason, `+{registrationId}+` is not part of these URLs by default.
  72. This URL is customizable in the DSL.
  73. For example, if you are migrating your existing relying party over to Spring Security, your asserting party may already be pointing to `GET /SLOService.saml2`.
  74. To reduce changes in configuration for the asserting party, you can configure the filter in the DSL like so:
  75. [tabs]
  76. ======
  77. Java::
  78. +
  79. [source,java,role="primary"]
  80. ----
  81. http
  82. .saml2Logout((saml2) -> saml2
  83. .logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
  84. .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2"))
  85. );
  86. ----
  87. ======
  88. You should also configure these endpoints in your `RelyingPartyRegistration`.
  89. == Customizing `<saml2:LogoutRequest>` Resolution
  90. It's common to need to set other values in the `<saml2:LogoutRequest>` than the defaults that Spring Security provides.
  91. By default, Spring Security will issue a `<saml2:LogoutRequest>` and supply:
  92. * The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation`
  93. * The `ID` attribute - a GUID
  94. * The `<Issuer>` element - from `RelyingPartyRegistration#getEntityId`
  95. * The `<NameID>` element - from `Authentication#getName`
  96. To add other values, you can use delegation, like so:
  97. [source,java]
  98. ----
  99. @Bean
  100. Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) {
  101. OpenSaml4LogoutRequestResolver logoutRequestResolver =
  102. new OpenSaml4LogoutRequestResolver(registrations);
  103. logoutRequestResolver.setParametersConsumer((parameters) -> {
  104. String name = ((Saml2AuthenticatedPrincipal) parameters.getAuthentication().getPrincipal()).getFirstAttribute("CustomAttribute");
  105. String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
  106. LogoutRequest logoutRequest = parameters.getLogoutRequest();
  107. NameID nameId = logoutRequest.getNameID();
  108. nameId.setValue(name);
  109. nameId.setFormat(format);
  110. });
  111. return logoutRequestResolver;
  112. }
  113. ----
  114. Then, you can supply your custom `Saml2LogoutRequestResolver` in the DSL as follows:
  115. [source,java]
  116. ----
  117. http
  118. .saml2Logout((saml2) -> saml2
  119. .logoutRequest((request) -> request
  120. .logoutRequestResolver(this.logoutRequestResolver)
  121. )
  122. );
  123. ----
  124. == Customizing `<saml2:LogoutResponse>` Resolution
  125. It's common to need to set other values in the `<saml2:LogoutResponse>` than the defaults that Spring Security provides.
  126. By default, Spring Security will issue a `<saml2:LogoutResponse>` and supply:
  127. * The `Destination` attribute - from `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation`
  128. * The `ID` attribute - a GUID
  129. * The `<Issuer>` element - from `RelyingPartyRegistration#getEntityId`
  130. * The `<Status>` element - `SUCCESS`
  131. To add other values, you can use delegation, like so:
  132. [source,java]
  133. ----
  134. @Bean
  135. public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationRepository registrations) {
  136. OpenSaml4LogoutResponseResolver logoutRequestResolver =
  137. new OpenSaml4LogoutResponseResolver(registrations);
  138. logoutRequestResolver.setParametersConsumer((parameters) -> {
  139. if (checkOtherPrevailingConditions(parameters.getRequest())) {
  140. parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT);
  141. }
  142. });
  143. return logoutRequestResolver;
  144. }
  145. ----
  146. Then, you can supply your custom `Saml2LogoutResponseResolver` in the DSL as follows:
  147. [source,java]
  148. ----
  149. http
  150. .saml2Logout((saml2) -> saml2
  151. .logoutRequest((request) -> request
  152. .logoutRequestResolver(this.logoutRequestResolver)
  153. )
  154. );
  155. ----
  156. == Customizing `<saml2:LogoutRequest>` Authentication
  157. To customize validation, you can implement your own `Saml2LogoutRequestValidator`.
  158. At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutRequestValidator` like so:
  159. [source,java]
  160. ----
  161. @Component
  162. public class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator {
  163. private final Saml2LogoutRequestValidator delegate = new OpenSamlLogoutRequestValidator();
  164. @Override
  165. public Saml2LogoutRequestValidator logout(Saml2LogoutRequestValidatorParameters parameters) {
  166. // verify signature, issuer, destination, and principal name
  167. Saml2LogoutValidatorResult result = delegate.authenticate(authentication);
  168. LogoutRequest logoutRequest = // ... parse using OpenSAML
  169. // perform custom validation
  170. }
  171. }
  172. ----
  173. Then, you can supply your custom `Saml2LogoutRequestValidator` in the DSL as follows:
  174. [source,java]
  175. ----
  176. http
  177. .saml2Logout((saml2) -> saml2
  178. .logoutRequest((request) -> request
  179. .logoutRequestAuthenticator(myOpenSamlLogoutRequestAuthenticator)
  180. )
  181. );
  182. ----
  183. == Customizing `<saml2:LogoutResponse>` Authentication
  184. To customize validation, you can implement your own `Saml2LogoutResponseValidator`.
  185. At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutResponseValidator` like so:
  186. [source,java]
  187. ----
  188. @Component
  189. public class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidator {
  190. private final Saml2LogoutResponseValidator delegate = new OpenSamlLogoutResponseValidator();
  191. @Override
  192. public Saml2LogoutValidatorResult logout(Saml2LogoutResponseValidatorParameters parameters) {
  193. // verify signature, issuer, destination, and status
  194. Saml2LogoutValidatorResult result = delegate.authenticate(parameters);
  195. LogoutResponse logoutResponse = // ... parse using OpenSAML
  196. // perform custom validation
  197. }
  198. }
  199. ----
  200. Then, you can supply your custom `Saml2LogoutResponseValidator` in the DSL as follows:
  201. [source,java]
  202. ----
  203. http
  204. .saml2Logout((saml2) -> saml2
  205. .logoutResponse((response) -> response
  206. .logoutResponseAuthenticator(myOpenSamlLogoutResponseAuthenticator)
  207. )
  208. );
  209. ----
  210. == Customizing `<saml2:LogoutRequest>` storage
  211. When your application sends a `<saml2:LogoutRequest>`, the value is stored in the session so that the `RelayState` parameter and the `InResponseTo` attribute in the `<saml2:LogoutResponse>` can be verified.
  212. If you want to store logout requests in some place other than the session, you can supply your custom implementation in the DSL, like so:
  213. [source,java]
  214. ----
  215. http
  216. .saml2Logout((saml2) -> saml2
  217. .logoutRequest((request) -> request
  218. .logoutRequestRepository(myCustomLogoutRequestRepository)
  219. )
  220. );
  221. ----