logout.adoc 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. [[servlet-saml2login-logout]]
  2. = Performing Single Logout
  3. Among its xref:servlet/authentication/logout.adoc[other logout mechanisms], 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 achieve this in Spring Boot in the following way:
  18. [source,yaml]
  19. ----
  20. spring:
  21. security:
  22. saml2:
  23. relyingparty:
  24. registration:
  25. metadata:
  26. signing.credentials: <3>
  27. - private-key-location: classpath:credentials/rp-private.key
  28. certificate-location: classpath:credentials/rp-certificate.crt
  29. singlelogout.url: "{baseUrl}/logout/saml2/slo" <2>
  30. assertingparty:
  31. metadata-uri: https://ap.example.com/metadata <1>
  32. ----
  33. <1> - The metadata URI of the IDP, which will indicate to your application its support of SLO
  34. <2> - The SLO endpoint in your application
  35. <3> - The signing credentials to sign ``<saml2:LogoutRequest>``s and ``<saml2:LogoutResponse>``s
  36. [NOTE]
  37. ----
  38. An asserting party supports Single Logout if their metadata includes the `<SingleLogoutService>` element in their metadata.
  39. ----
  40. And that's it!
  41. Spring Security's logout support offers a number of configuration points.
  42. Consider the following use cases:
  43. * Understand how the above <<_startup_expectations, minimal configuration works>>
  44. * Get a picture of <<architecture, the overall architecture>>
  45. * Allow users to <<separating-local-saml2-logout, logout out of the app only>>
  46. * Customize <<_configuring_logout_endpoints, logout endpoints>>
  47. * Storing `<saml2:LogoutRequests>` somewhere <<_customizing_storage, other than the session>>
  48. === Startup Expectations
  49. When these properties are used, in addition to login, SAML 2.0 Service Provider will automatically configure itself facilitate logout by way of ``<saml2:LogoutRequest>``s and ``<saml2:LogoutResponse>``s using either RP- or AP-initiated logout.
  50. It achieves this through a deterministic startup process:
  51. 1. Query the Identity Server Metadata endpoint for the `<SingleLogoutService>` element
  52. 2. Scan the metadata and cache any public signature verification keys
  53. 3. Prepare the appropriate endpoints
  54. A consequence of this process is that the identity server must be up and receiving requests in order for Service Provider to successfully start up.
  55. [NOTE]
  56. If the identity server is down when Service Provider queries it (given appropriate timeouts), then startup will fail.
  57. === Runtime Expectations
  58. Given the above configuration any logged-in user can send a `POST /logout` to your application to perform RP-initiated SLO.
  59. Your application will then do the following:
  60. 1. Logout the user and invalidate the session
  61. 2. Produce a `<saml2:LogoutRequest>` and POST it to the associated asserting party's SLO endpoint
  62. 3. Then, if the asserting party responds with a `<saml2:LogoutResponse>`, the application with verify it and redirect to the configured success endpoint
  63. Also, your application can participate in an AP-initiated logout when the asserting party sends a `<saml2:LogoutRequest>` to `/logout/saml2/slo`.
  64. When this happens, your application will do the following:
  65. 1. Verify the `<saml2:LogoutRequest>`
  66. 2. Logout the user and invalidate the session
  67. 3. Produce a `<saml2:LogoutResponse>` and POST it back to the asserting party's SLO endpoint
  68. == Minimal Configuration Sans Boot
  69. Instead of Boot properties, you can also achieve the same outcome by publishing the beans directly like so:
  70. [tabs]
  71. ======
  72. Java::
  73. +
  74. [source,java,role="primary"]
  75. ----
  76. @Configuration
  77. public class SecurityConfig {
  78. @Value("${private.key}") RSAPrivateKey key;
  79. @Value("${public.certificate}") X509Certificate certificate;
  80. @Bean
  81. RelyingPartyRegistrationRepository registrations() {
  82. Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate);
  83. RelyingPartyRegistration registration = RelyingPartyRegistrations
  84. .fromMetadataLocation("https://ap.example.org/metadata") <1>
  85. .registrationId("metadata")
  86. .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") <2>
  87. .signingX509Credentials((signing) -> signing.add(credential)) <3>
  88. .build();
  89. return new InMemoryRelyingPartyRegistrationRepository(registration);
  90. }
  91. @Bean
  92. SecurityFilterChain web(HttpSecurity http) throws Exception {
  93. http
  94. .authorizeHttpRequests((authorize) -> authorize
  95. .anyRequest().authenticated()
  96. )
  97. .saml2Login(withDefaults())
  98. .saml2Logout(withDefaults()); <4>
  99. return http.build();
  100. }
  101. }
  102. ----
  103. Kotlin::
  104. +
  105. [source,kotlin,role="secondary"]
  106. ----
  107. @Configuration
  108. class SecurityConfig(@Value("${private.key}") val key: RSAPrivateKey,
  109. @Value("${public.certificate}") val certificate: X509Certificate) {
  110. @Bean
  111. fun registrations(): RelyingPartyRegistrationRepository {
  112. val credential = Saml2X509Credential.signing(key, certificate)
  113. val registration = RelyingPartyRegistrations
  114. .fromMetadataLocation("https://ap.example.org/metadata") <1>
  115. .registrationId("metadata")
  116. .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") <2>
  117. .signingX509Credentials({ signing: List<Saml2X509Credential> -> signing.add(credential) }) <3>
  118. .build()
  119. return InMemoryRelyingPartyRegistrationRepository(registration)
  120. }
  121. @Bean
  122. fun web(http: HttpSecurity): SecurityFilterChain {
  123. http {
  124. authorizeHttpRequests {
  125. anyRequest = authenticated
  126. }
  127. saml2Login {
  128. }
  129. saml2Logout { <4>
  130. }
  131. }
  132. return http.build()
  133. }
  134. }
  135. ----
  136. ======
  137. <1> - The metadata URI of the IDP, which will indicate to your application its support of SLO
  138. <2> - The SLO endpoint in your application
  139. <3> - The signing credentials to sign ``<saml2:LogoutRequest>``s and ``<saml2:LogoutResponse>``s, which you can also add to xref:servlet/saml2/login/overview.adoc#servlet-saml2login-rpr-duplicated[multiple relying parties]
  140. <4> - Second, indicate that your application wants to use SAML SLO to logout the end user
  141. [NOTE]
  142. Adding `saml2Logout` adds the capability for logout to your service provider as a whole.
  143. Because it is an optional capability, you need to enable it for each individual `RelyingPartyRegistration`.
  144. You do this by setting the `RelyingPartyRegistration.Builder#singleLogoutServiceLocation` property as seen above.
  145. [[architecture]]
  146. == How Saml 2.0 Logout Works
  147. Next, let's see the architectural components that Spring Security uses to support https://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf#page=37[SAML 2.0 Logout] in servlet-based applications, like the one we just saw.
  148. For RP-initiated logout:
  149. image:{icondir}/number_1.png[] Spring Security executes its xref:servlet/authentication/logout.adoc#logout-architecture[logout flow], calling its ``LogoutHandler``s to invalidate the session and perform other cleanup.
  150. It then invokes the javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler[].
  151. image:{icondir}/number_2.png[] The logout success handler uses an instance of
  152. javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver[] to create, sign, and serialize a `<saml2:LogoutRequest>`.
  153. It uses the keys and configuration from the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] that is associated with the current `Saml2AuthenticatedPrincipal`.
  154. Then, it redirect-POSTs the `<saml2:LogoutRequest>` to the asserting party SLO endpoint
  155. The browser hands control over to the asserting party.
  156. If the asserting party redirects back (which it may not), then the application proceeds to step image:{icondir}/number_3.png[].
  157. image:{icondir}/number_3.png[] The javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter[] deserializes, verifies, and processes the `<saml2:LogoutResponse>` with its javadoc:org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator[].
  158. image:{icondir}/number_4.png[] If valid, then it completes the local logout flow by redirecting to `/login?logout`, or whatever has been configured.
  159. If invalid, then it responds with a 400.
  160. For AP-initiated logout:
  161. image:{icondir}/number_1.png[] The javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter[] deserializes, verifies, and processes the `<saml2:LogoutRequest>` with its javadoc:org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator[].
  162. image:{icondir}/number_2.png[] If valid, then the filter calls the configured ``LogoutHandler``s, invalidating the session and performing other cleanup.
  163. image:{icondir}/number_3.png[] It uses a javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver[] to create, sign and serialize a `<saml2:LogoutResponse>`.
  164. It uses the keys and configuration from the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] derived from the endpoint or from the contents of the `<saml2:LogoutRequest>`.
  165. Then, it redirect-POSTs the `<saml2:LogoutResponse>` to the asserting party SLO endpoint.
  166. The browser hands control over to the asserting party.
  167. image:{icondir}/number_4.png[] If invalid, then it https://github.com/spring-projects/spring-security/pull/14676[responds with a 400].
  168. == Configuring Logout Endpoints
  169. There are three behaviors that can be triggered by different endpoints:
  170. * RP-initiated logout, which allows an authenticated user to `POST` and trigger the logout process by sending the asserting party a `<saml2:LogoutRequest>`
  171. * AP-initiated logout, which allows an asserting party to send a `<saml2:LogoutRequest>` to the application
  172. * AP logout response, which allows an asserting party to send a `<saml2:LogoutResponse>` in response to the RP-initiated `<saml2:LogoutRequest>`
  173. The first is triggered by performing normal `POST /logout` when the principal is of type `Saml2AuthenticatedPrincipal`.
  174. The second is triggered by POSTing to the `/logout/saml2/slo` endpoint with a `SAMLRequest` signed by the asserting party.
  175. The third is triggered by POSTing to the `/logout/saml2/slo` endpoint with a `SAMLResponse` signed by the asserting party.
  176. Because the user is already logged in or the original Logout Request is known, the `registrationId` is already known.
  177. For this reason, `+{registrationId}+` is not part of these URLs by default.
  178. This URL is customizable in the DSL.
  179. 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`.
  180. To reduce changes in configuration for the asserting party, you can configure the filter in the DSL like so:
  181. [tabs]
  182. ======
  183. Java::
  184. +
  185. [source,java,role="primary"]
  186. ----
  187. http
  188. .saml2Logout((saml2) -> saml2
  189. .logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
  190. .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2"))
  191. );
  192. ----
  193. Kotlin::
  194. +
  195. [source,kotlin,role="secondary"]
  196. ----
  197. http {
  198. saml2Logout {
  199. logoutRequest {
  200. logoutUrl = "/SLOService.saml2"
  201. }
  202. logoutResponse {
  203. logoutUrl = "/SLOService.saml2"
  204. }
  205. }
  206. }
  207. ----
  208. ======
  209. You should also configure these endpoints in your `RelyingPartyRegistration`.
  210. Also, you can customize the endpoint for triggering logout locally like so:
  211. [tabs]
  212. ======
  213. Java::
  214. +
  215. [source,java,role="primary"]
  216. ----
  217. http
  218. .saml2Logout((saml2) -> saml2.logoutUrl("/saml2/logout"));
  219. ----
  220. Kotlin::
  221. +
  222. [source,kotlin,role="secondary"]
  223. ----
  224. http {
  225. saml2Logout {
  226. logoutUrl = "/saml2/logout"
  227. }
  228. }
  229. ----
  230. ======
  231. [[separating-local-saml2-logout]]
  232. === Separating Local Logout from SAML 2.0 Logout
  233. In some cases, you may want to expose one logout endpoint for local logout and another for RP-initiated SLO.
  234. Like is the case with other logout mechanisms, you can register more than one, so long as they each have a different endpoint.
  235. So, for example, you can wire the DSL like so:
  236. [tabs]
  237. ======
  238. Java::
  239. +
  240. [source,java,role="primary"]
  241. ----
  242. http
  243. .logout((logout) -> logout.logoutUrl("/logout"))
  244. .saml2Logout((saml2) -> saml2.logoutUrl("/saml2/logout"));
  245. ----
  246. Kotlin::
  247. +
  248. [source,kotlin,role="secondary"]
  249. ----
  250. http {
  251. logout {
  252. logoutUrl = "/logout"
  253. }
  254. saml2Logout {
  255. logoutUrl = "/saml2/logout"
  256. }
  257. }
  258. ----
  259. ======
  260. and now if a client sends a `POST /logout`, the session will be cleared, but there won't be a `<saml2:LogoutRequest>` sent to the asserting party.
  261. But, if the client sends a `POST /saml2/logout`, then the application will initiate SAML 2.0 SLO as normal.
  262. == Customizing `<saml2:LogoutRequest>` Resolution
  263. It's common to need to set other values in the `<saml2:LogoutRequest>` than the defaults that Spring Security provides.
  264. By default, Spring Security will issue a `<saml2:LogoutRequest>` and supply:
  265. * The `DestinationValidator` attribute - from `RelyingPartyRegistration#getAssertingPartyMetadata#getSingleLogoutServiceLocation`
  266. * The `ID` attribute - a GUID
  267. * The `<Issuer>` element - from `RelyingPartyRegistration#getEntityId`
  268. * The `<NameID>` element - from `Authentication#getName`
  269. To add other values, you can use delegation, like so:
  270. [tabs]
  271. ======
  272. Java::
  273. +
  274. [source,java,role="primary"]
  275. ----
  276. @Bean
  277. Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) {
  278. OpenSaml4LogoutRequestResolver logoutRequestResolver =
  279. new OpenSaml4LogoutRequestResolver(registrations);
  280. logoutRequestResolver.setParametersConsumer((parameters) -> {
  281. String name = ((Saml2AuthenticatedPrincipal) parameters.getAuthentication().getPrincipal()).getFirstAttribute("CustomAttribute");
  282. String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
  283. LogoutRequest logoutRequest = parameters.getLogoutRequest();
  284. NameID nameId = logoutRequest.getNameID();
  285. nameId.setValue(name);
  286. nameId.setFormat(format);
  287. });
  288. return logoutRequestResolver;
  289. }
  290. ----
  291. Kotlin::
  292. +
  293. [source,kotlin,role="secondary"]
  294. ----
  295. @Bean
  296. open fun logoutRequestResolver(registrations:RelyingPartyRegistrationRepository?): Saml2LogoutRequestResolver {
  297. val logoutRequestResolver = OpenSaml4LogoutRequestResolver(registrations)
  298. logoutRequestResolver.setParametersConsumer { parameters: LogoutRequestParameters ->
  299. val name: String = (parameters.getAuthentication().getPrincipal() as Saml2AuthenticatedPrincipal).getFirstAttribute("CustomAttribute")
  300. val format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
  301. val logoutRequest: LogoutRequest = parameters.getLogoutRequest()
  302. val nameId: NameID = logoutRequest.getNameID()
  303. nameId.setValue(name)
  304. nameId.setFormat(format)
  305. }
  306. return logoutRequestResolver
  307. }
  308. ----
  309. ======
  310. Then, you can supply your custom `Saml2LogoutRequestResolver` in the DSL as follows:
  311. [tabs]
  312. ======
  313. Java::
  314. +
  315. [source,java,role="primary"]
  316. ----
  317. http
  318. .saml2Logout((saml2) -> saml2
  319. .logoutRequest((request) -> request
  320. .logoutRequestResolver(this.logoutRequestResolver)
  321. )
  322. );
  323. ----
  324. Kotlin::
  325. +
  326. [source,kotlin,role="secondary"]
  327. ----
  328. http {
  329. saml2Logout {
  330. logoutRequest {
  331. logoutRequestResolver = this.logoutRequestResolver
  332. }
  333. }
  334. }
  335. ----
  336. ======
  337. == Customizing `<saml2:LogoutResponse>` Resolution
  338. It's common to need to set other values in the `<saml2:LogoutResponse>` than the defaults that Spring Security provides.
  339. By default, Spring Security will issue a `<saml2:LogoutResponse>` and supply:
  340. * The `DestinationValidator` attribute - from `RelyingPartyRegistration#getAssertingPartyMetadata#getSingleLogoutServiceResponseLocation`
  341. * The `ID` attribute - a GUID
  342. * The `<Issuer>` element - from `RelyingPartyRegistration#getEntityId`
  343. * The `<Status>` element - `SUCCESS`
  344. To add other values, you can use delegation, like so:
  345. [tabs]
  346. ======
  347. Java::
  348. +
  349. [source,java,role="primary"]
  350. ----
  351. @Bean
  352. public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationRepository registrations) {
  353. OpenSaml4LogoutResponseResolver logoutRequestResolver =
  354. new OpenSaml4LogoutResponseResolver(registrations);
  355. logoutRequestResolver.setParametersConsumer((parameters) -> {
  356. if (checkOtherPrevailingConditions(parameters.getRequest())) {
  357. parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT);
  358. }
  359. });
  360. return logoutRequestResolver;
  361. }
  362. ----
  363. Kotlin::
  364. +
  365. [source,kotlin,role="secondary"]
  366. ----
  367. @Bean
  368. open fun logoutResponseResolver(registrations: RelyingPartyRegistrationRepository?): Saml2LogoutResponseResolver {
  369. val logoutRequestResolver = OpenSaml4LogoutResponseResolver(registrations)
  370. logoutRequestResolver.setParametersConsumer { LogoutResponseParameters parameters ->
  371. if (checkOtherPrevailingConditions(parameters.getRequest())) {
  372. parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT)
  373. }
  374. }
  375. return logoutRequestResolver
  376. }
  377. ----
  378. ======
  379. Then, you can supply your custom `Saml2LogoutResponseResolver` in the DSL as follows:
  380. [tabs]
  381. ======
  382. Java::
  383. +
  384. [source,java,role="primary"]
  385. ----
  386. http
  387. .saml2Logout((saml2) -> saml2
  388. .logoutRequest((request) -> request
  389. .logoutRequestResolver(this.logoutRequestResolver)
  390. )
  391. );
  392. ----
  393. Kotlin::
  394. +
  395. [source,kotlin,role="secondary"]
  396. ----
  397. http {
  398. saml2Logout {
  399. logoutRequest {
  400. logoutRequestResolver = this.logoutRequestResolver
  401. }
  402. }
  403. }
  404. ----
  405. ======
  406. == Customizing `<saml2:LogoutRequest>` Authentication
  407. To customize validation, you can implement your own `Saml2LogoutRequestValidator`.
  408. At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutRequestValidator` like so:
  409. [tabs]
  410. ======
  411. Java::
  412. +
  413. [source,java,role="primary"]
  414. ----
  415. @Component
  416. public class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator {
  417. private final Saml2LogoutRequestValidator delegate = new OpenSaml5LogoutRequestValidator();
  418. @Override
  419. public Saml2LogoutRequestValidator logout(Saml2LogoutRequestValidatorParameters parameters) {
  420. // verify signature, issuer, destination, and principal name
  421. Saml2LogoutValidatorResult result = delegate.authenticate(authentication);
  422. LogoutRequest logoutRequest = // ... parse using OpenSAML
  423. // perform custom validation
  424. }
  425. }
  426. ----
  427. Kotlin::
  428. +
  429. [source,kotlin,role="secondary"]
  430. ----
  431. @Component
  432. open class MyOpenSamlLogoutRequestValidator: Saml2LogoutRequestValidator {
  433. private val delegate = OpenSaml5LogoutRequestValidator()
  434. @Override
  435. fun logout(parameters: Saml2LogoutRequestValidatorParameters): Saml2LogoutRequestValidator {
  436. // verify signature, issuer, destination, and principal name
  437. val result = delegate.authenticate(authentication)
  438. val logoutRequest: LogoutRequest = // ... parse using OpenSAML
  439. // perform custom validation
  440. }
  441. }
  442. ----
  443. ======
  444. Then, you can supply your custom `Saml2LogoutRequestValidator` in the DSL as follows:
  445. [tabs]
  446. ======
  447. Java::
  448. +
  449. [source,java,role="primary"]
  450. ----
  451. http
  452. .saml2Logout((saml2) -> saml2
  453. .logoutRequest((request) -> request
  454. .logoutRequestValidator(myOpenSamlLogoutRequestValidator)
  455. )
  456. );
  457. ----
  458. Kotlin::
  459. +
  460. [source,kotlin,role="secondary"]
  461. ----
  462. http {
  463. saml2Logout {
  464. logoutRequest {
  465. logoutRequestValidator = myOpenSamlLogoutRequestValidator
  466. }
  467. }
  468. }
  469. ----
  470. ======
  471. == Customizing `<saml2:LogoutResponse>` Authentication
  472. To customize validation, you can implement your own `Saml2LogoutResponseValidator`.
  473. At this point, the validation is minimal, so you may be able to first delegate to the default `Saml2LogoutResponseValidator` like so:
  474. [tabs]
  475. ======
  476. Java::
  477. +
  478. [source,java,role="primary"]
  479. ----
  480. @Component
  481. public class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidator {
  482. private final Saml2LogoutResponseValidator delegate = new OpenSaml5LogoutResponseValidator();
  483. @Override
  484. public Saml2LogoutValidatorResult logout(Saml2LogoutResponseValidatorParameters parameters) {
  485. // verify signature, issuer, destination, and status
  486. Saml2LogoutValidatorResult result = delegate.authenticate(parameters);
  487. LogoutResponse logoutResponse = // ... parse using OpenSAML
  488. // perform custom validation
  489. }
  490. }
  491. ----
  492. Kotlin::
  493. +
  494. [source,kotlin,role="secondary"]
  495. ----
  496. @Component
  497. open class MyOpenSamlLogoutResponseValidator: Saml2LogoutResponseValidator {
  498. private val delegate = OpenSaml4LogoutResponseValidator()
  499. @Override
  500. fun logout(parameters: Saml2LogoutResponseValidatorParameters): Saml2LogoutResponseValidator {
  501. // verify signature, issuer, destination, and status
  502. val result = delegate.authenticate(authentication)
  503. val logoutResponse: LogoutResponse = // ... parse using OpenSAML
  504. // perform custom validation
  505. }
  506. }
  507. ----
  508. ======
  509. Then, you can supply your custom `Saml2LogoutResponseValidator` in the DSL as follows:
  510. [tabs]
  511. ======
  512. Java::
  513. +
  514. [source,java,role="primary"]
  515. ----
  516. http
  517. .saml2Logout((saml2) -> saml2
  518. .logoutResponse((response) -> response
  519. .logoutResponseAuthenticator(myOpenSamlLogoutResponseAuthenticator)
  520. )
  521. );
  522. ----
  523. Kotlin::
  524. +
  525. [source,kotlin,role="secondary"]
  526. ----
  527. http {
  528. saml2Logout {
  529. logoutResponse {
  530. logoutResponseValidator = myOpenSamlLogoutResponseValidator
  531. }
  532. }
  533. }
  534. ----
  535. ======
  536. == Customizing `<saml2:LogoutRequest>` storage
  537. 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.
  538. 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:
  539. [tabs]
  540. ======
  541. Java::
  542. +
  543. [source,java,role="primary"]
  544. ----
  545. http
  546. .saml2Logout((saml2) -> saml2
  547. .logoutRequest((request) -> request
  548. .logoutRequestRepository(myCustomLogoutRequestRepository)
  549. )
  550. );
  551. ----
  552. Kotlin::
  553. +
  554. [source,kotlin,role="secondary"]
  555. ----
  556. http {
  557. saml2Logout {
  558. logoutRequest {
  559. logoutRequestRepository = myCustomLogoutRequestRepository
  560. }
  561. }
  562. }
  563. ----
  564. ======
  565. [[jc-logout-references]]
  566. == Further Logout-Related References
  567. - xref:servlet/test/mockmvc/logout.adoc#test-logout[Testing Logout]
  568. - xref:servlet/integrations/servlet-api.adoc#servletapi-logout[HttpServletRequest.logout()]
  569. - xref:servlet/exploits/csrf.adoc#csrf-considerations-logout[Logging Out] in section CSRF Caveats