(window.webpackJsonp=window.webpackJsonp||[]).push([[299],{725:function(e,t,o){"use strict";o.r(t);var a=o(56),s=Object(a.a)({},(function(){var e=this,t=e.$createElement,o=e._self._c||t;return o("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[o("h1",{attrs:{id:"performing-single-logout"}},[o("a",{staticClass:"header-anchor",attrs:{href:"#performing-single-logout"}},[e._v("#")]),e._v(" Performing Single Logout")]),e._v(" "),o("p",[e._v("Spring Security ships with support for RP- and AP-initiated SAML 2.0 Single Logout.")]),e._v(" "),o("p",[e._v("Briefly, there are two use cases Spring Security supports:")]),e._v(" "),o("ul",[o("li",[o("p",[o("strong",[e._v("RP-Initiated")]),e._v(" - Your application has an endpoint that, when POSTed to, will logout the user and send a "),o("code",[e._v("saml2:LogoutRequest")]),e._v(" to the asserting party.\nThereafter, the asserting party will send back a "),o("code",[e._v("saml2:LogoutResponse")]),e._v(" and allow your application to respond")])]),e._v(" "),o("li",[o("p",[o("strong",[e._v("AP-Initiated")]),e._v(" - Your application has an endpoint that will receive a "),o("code",[e._v("saml2:LogoutRequest")]),e._v(" from the asserting party.\nYour application will complete its logout at that point and then send a "),o("code",[e._v("saml2:LogoutResponse")]),e._v(" to the asserting party.")])])]),e._v(" "),o("table",[o("thead",[o("tr",[o("th"),e._v(" "),o("th",[e._v("In the "),o("strong",[e._v("AP-Initiated")]),e._v(" scenario, any local redirection that your application would do post-logout is rendered moot."),o("br"),e._v("Once your application sends a "),o("code",[e._v("saml2:LogoutResponse")]),e._v(", it no longer has control of the browser.")])])]),e._v(" "),o("tbody")]),e._v(" "),o("h2",{attrs:{id:"minimal-configuration-for-single-logout"}},[o("a",{staticClass:"header-anchor",attrs:{href:"#minimal-configuration-for-single-logout"}},[e._v("#")]),e._v(" Minimal Configuration for Single Logout")]),e._v(" "),o("p",[e._v("To use Spring Security’s SAML 2.0 Single Logout feature, you will need the following things:")]),e._v(" "),o("ul",[o("li",[o("p",[e._v("First, the asserting party must support SAML 2.0 Single Logout")])]),e._v(" "),o("li",[o("p",[e._v("Second, the asserting party should be configured to sign and POST "),o("code",[e._v("saml2:LogoutRequest")]),e._v(" s and "),o("code",[e._v("saml2:LogoutResponse")]),e._v(" s your application’s "),o("code",[e._v("/logout/saml2/slo")]),e._v(" endpoint")])]),e._v(" "),o("li",[o("p",[e._v("Third, your application must have a PKCS#8 private key and X.509 certificate for signing "),o("code",[e._v("saml2:LogoutRequest")]),e._v(" s and "),o("code",[e._v("saml2:LogoutResponse")]),e._v(" s")])])]),e._v(" "),o("p",[e._v("You can begin from the initial minimal example and add the following configuration:")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v('@Value("${private.key}") RSAPrivateKey key;\n@Value("${public.certificate}") X509Certificate certificate;\n\n@Bean\nRelyingPartyRegistrationRepository registrations() {\n Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate);\n RelyingPartyRegistration registration = RelyingPartyRegistrations\n .fromMetadataLocation("https://ap.example.org/metadata")\n .registrationId("id")\n .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo")\n .signingX509Credentials((signing) -> signing.add(credential)) (1)\n .build();\n return new InMemoryRelyingPartyRegistrationRepository(registration);\n}\n\n@Bean\nSecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception {\n http\n .authorizeHttpRequests((authorize) -> authorize\n .anyRequest().authenticated()\n )\n .saml2Login(withDefaults())\n .saml2Logout(withDefaults()); (2)\n\n return http.build();\n}\n')])])]),o("table",[o("thead",[o("tr",[o("th",[o("strong",[e._v("1")])]),e._v(" "),o("th",[e._v("- First, add your signing key to the "),o("code",[e._v("RelyingPartyRegistration")]),e._v(" instance or to "),o("RouterLink",{attrs:{to:"/en/spring-security/login/overview.html#servlet-saml2login-rpr-duplicated"}},[e._v("multiple instances")])],1)])]),e._v(" "),o("tbody",[o("tr",[o("td",[o("strong",[e._v("2")])]),e._v(" "),o("td",[e._v("- Second, indicate that your application wants to use SAML SLO to logout the end user")])])])]),e._v(" "),o("h3",{attrs:{id:"runtime-expectations"}},[o("a",{staticClass:"header-anchor",attrs:{href:"#runtime-expectations"}},[e._v("#")]),e._v(" Runtime Expectations")]),e._v(" "),o("p",[e._v("Given the above configuration any logged in user can send a "),o("code",[e._v("POST /logout")]),e._v(" to your application to perform RP-initiated SLO.\nYour application will then do the following:")]),e._v(" "),o("ol",[o("li",[o("p",[e._v("Logout the user and invalidate the session")])]),e._v(" "),o("li",[o("p",[e._v("Use a "),o("code",[e._v("Saml2LogoutRequestResolver")]),e._v(" to create, sign, and serialize a "),o("code",[e._v("")]),e._v(" based on the "),o("RouterLink",{attrs:{to:"/en/spring-security/login/overview.html#servlet-saml2login-relyingpartyregistration"}},[o("code",[e._v("RelyingPartyRegistration")])]),e._v(" associated with the currently logged-in user.")],1)]),e._v(" "),o("li",[o("p",[e._v("Send a redirect or post to the asserting party based on the "),o("RouterLink",{attrs:{to:"/en/spring-security/login/overview.html#servlet-saml2login-relyingpartyregistration"}},[o("code",[e._v("RelyingPartyRegistration")])])],1)]),e._v(" "),o("li",[o("p",[e._v("Deserialize, verify, and process the "),o("code",[e._v("")]),e._v(" sent by the asserting party")])]),e._v(" "),o("li",[o("p",[e._v("Redirect to any configured successful logout endpoint")])])]),e._v(" "),o("p",[e._v("Also, your application can participate in an AP-initiated logout when the asserting party sends a "),o("code",[e._v("")]),e._v(" to "),o("code",[e._v("/logout/saml2/slo")]),e._v(":")]),e._v(" "),o("ol",[o("li",[o("p",[e._v("Use a "),o("code",[e._v("Saml2LogoutRequestHandler")]),e._v(" to deserialize, verify, and process the "),o("code",[e._v("")]),e._v(" sent by the asserting party")])]),e._v(" "),o("li",[o("p",[e._v("Logout the user and invalidate the session")])]),e._v(" "),o("li",[o("p",[e._v("Create, sign, and serialize a "),o("code",[e._v("")]),e._v(" based on the "),o("RouterLink",{attrs:{to:"/en/spring-security/login/overview.html#servlet-saml2login-relyingpartyregistration"}},[o("code",[e._v("RelyingPartyRegistration")])]),e._v(" associated with the just logged-out user")],1)]),e._v(" "),o("li",[o("p",[e._v("Send a redirect or post to the asserting party based on the "),o("RouterLink",{attrs:{to:"/en/spring-security/login/overview.html#servlet-saml2login-relyingpartyregistration"}},[o("code",[e._v("RelyingPartyRegistration")])])],1)])]),e._v(" "),o("table",[o("thead",[o("tr",[o("th"),e._v(" "),o("th",[e._v("Adding "),o("code",[e._v("saml2Logout")]),e._v(" adds the capability for logout to the service provider."),o("br"),e._v("Because it is an optional capability, you need to enable it for each individual "),o("code",[e._v("RelyingPartyRegistration")]),e._v("."),o("br"),e._v("You can do this by setting the "),o("code",[e._v("RelyingPartyRegistration.Builder#singleLogoutServiceLocation")]),e._v(" property.")])])]),e._v(" "),o("tbody")]),e._v(" "),o("h2",{attrs:{id:"configuring-logout-endpoints"}},[o("a",{staticClass:"header-anchor",attrs:{href:"#configuring-logout-endpoints"}},[e._v("#")]),e._v(" Configuring Logout Endpoints")]),e._v(" "),o("p",[e._v("There are three behaviors that can be triggered by different endpoints:")]),e._v(" "),o("ul",[o("li",[o("p",[e._v("RP-initiated logout, which allows an authenticated user to "),o("code",[e._v("POST")]),e._v(" and trigger the logout process by sending the asserting party a "),o("code",[e._v("")])])]),e._v(" "),o("li",[o("p",[e._v("AP-initiated logout, which allows an asserting party to send a "),o("code",[e._v("")]),e._v(" to the application")])]),e._v(" "),o("li",[o("p",[e._v("AP logout response, which allows an asserting party to send a "),o("code",[e._v("")]),e._v(" in response to the RP-initiated "),o("code",[e._v("")])])])]),e._v(" "),o("p",[e._v("The first is triggered by performing normal "),o("code",[e._v("POST /logout")]),e._v(" when the principal is of type "),o("code",[e._v("Saml2AuthenticatedPrincipal")]),e._v(".")]),e._v(" "),o("p",[e._v("The second is triggered by POSTing to the "),o("code",[e._v("/logout/saml2/slo")]),e._v(" endpoint with a "),o("code",[e._v("SAMLRequest")]),e._v(" signed by the asserting party.")]),e._v(" "),o("p",[e._v("The third is triggered by POSTing to the "),o("code",[e._v("/logout/saml2/slo")]),e._v(" endpoint with a "),o("code",[e._v("SAMLResponse")]),e._v(" signed by the asserting party.")]),e._v(" "),o("p",[e._v("Because the user is already logged in or the original Logout Request is known, the "),o("code",[e._v("registrationId")]),e._v(" is already known.\nFor this reason, "),o("code",[e._v("{registrationId}")]),e._v(" is not part of these URLs by default.")]),e._v(" "),o("p",[e._v("This URL is customizable in the DSL.")]),e._v(" "),o("p",[e._v("For example, if you are migrating your existing relying party over to Spring Security, your asserting party may already be pointing to "),o("code",[e._v("GET /SLOService.saml2")]),e._v(".\nTo reduce changes in configuration for the asserting party, you can configure the filter in the DSL like so:")]),e._v(" "),o("p",[e._v("Java")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v('http\n .saml2Logout((saml2) -> saml2\n .logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))\n .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2"))\n );\n')])])]),o("p",[e._v("You should also configure these endpoints in your "),o("code",[e._v("RelyingPartyRegistration")]),e._v(".")]),e._v(" "),o("h2",{attrs:{id:"customizing-saml2-logoutrequest-resolution"}},[o("a",{staticClass:"header-anchor",attrs:{href:"#customizing-saml2-logoutrequest-resolution"}},[e._v("#")]),e._v(" Customizing "),o("code",[e._v("")]),e._v(" Resolution")]),e._v(" "),o("p",[e._v("It’s common to need to set other values in the "),o("code",[e._v("")]),e._v(" than the defaults that Spring Security provides.")]),e._v(" "),o("p",[e._v("By default, Spring Security will issue a "),o("code",[e._v("")]),e._v(" and supply:")]),e._v(" "),o("ul",[o("li",[o("p",[e._v("The "),o("code",[e._v("Destination")]),e._v(" attribute - from "),o("code",[e._v("RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation")])])]),e._v(" "),o("li",[o("p",[e._v("The "),o("code",[e._v("ID")]),e._v(" attribute - a GUID")])]),e._v(" "),o("li",[o("p",[e._v("The "),o("code",[e._v("")]),e._v(" element - from "),o("code",[e._v("RelyingPartyRegistration#getEntityId")])])]),e._v(" "),o("li",[o("p",[e._v("The "),o("code",[e._v("")]),e._v(" element - from "),o("code",[e._v("Authentication#getName")])])])]),e._v(" "),o("p",[e._v("To add other values, you can use delegation, like so:")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v('@Bean\nSaml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationResolver registrationResolver) {\n\tOpenSaml4LogoutRequestResolver logoutRequestResolver\n\t\t\tnew OpenSaml4LogoutRequestResolver(registrationResolver);\n\tlogoutRequestResolver.setParametersConsumer((parameters) -> {\n\t\tString name = ((Saml2AuthenticatedPrincipal) parameters.getAuthentication().getPrincipal()).getFirstAttribute("CustomAttribute");\n\t\tString format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";\n\t\tLogoutRequest logoutRequest = parameters.getLogoutRequest();\n\t\tNameID nameId = logoutRequest.getNameID();\n\t\tnameId.setValue(name);\n\t\tnameId.setFormat(format);\n\t});\n\treturn logoutRequestResolver;\n}\n')])])]),o("p",[e._v("Then, you can supply your custom "),o("code",[e._v("Saml2LogoutRequestResolver")]),e._v(" in the DSL as follows:")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v("http\n .saml2Logout((saml2) -> saml2\n .logoutRequest((request) -> request\n .logoutRequestResolver(this.logoutRequestResolver)\n )\n );\n")])])]),o("h2",{attrs:{id:"customizing-saml2-logoutresponse-resolution"}},[o("a",{staticClass:"header-anchor",attrs:{href:"#customizing-saml2-logoutresponse-resolution"}},[e._v("#")]),e._v(" Customizing "),o("code",[e._v("")]),e._v(" Resolution")]),e._v(" "),o("p",[e._v("It’s common to need to set other values in the "),o("code",[e._v("")]),e._v(" than the defaults that Spring Security provides.")]),e._v(" "),o("p",[e._v("By default, Spring Security will issue a "),o("code",[e._v("")]),e._v(" and supply:")]),e._v(" "),o("ul",[o("li",[o("p",[e._v("The "),o("code",[e._v("Destination")]),e._v(" attribute - from "),o("code",[e._v("RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation")])])]),e._v(" "),o("li",[o("p",[e._v("The "),o("code",[e._v("ID")]),e._v(" attribute - a GUID")])]),e._v(" "),o("li",[o("p",[e._v("The "),o("code",[e._v("")]),e._v(" element - from "),o("code",[e._v("RelyingPartyRegistration#getEntityId")])])]),e._v(" "),o("li",[o("p",[e._v("The "),o("code",[e._v("")]),e._v(" element - "),o("code",[e._v("SUCCESS")])])])]),e._v(" "),o("p",[e._v("To add other values, you can use delegation, like so:")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v("@Bean\npublic Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationResolver registrationResolver) {\n\tOpenSaml4LogoutResponseResolver logoutRequestResolver =\n\t\t\tnew OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);\n\tlogoutRequestResolver.setParametersConsumer((parameters) -> {\n\t\tif (checkOtherPrevailingConditions(parameters.getRequest())) {\n\t\t\tparameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT);\n\t\t}\n\t});\n\treturn logoutRequestResolver;\n}\n")])])]),o("p",[e._v("Then, you can supply your custom "),o("code",[e._v("Saml2LogoutResponseResolver")]),e._v(" in the DSL as follows:")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v("http\n .saml2Logout((saml2) -> saml2\n .logoutRequest((request) -> request\n .logoutRequestResolver(this.logoutRequestResolver)\n )\n );\n")])])]),o("h2",{attrs:{id:"customizing-saml2-logoutrequest-authentication"}},[o("a",{staticClass:"header-anchor",attrs:{href:"#customizing-saml2-logoutrequest-authentication"}},[e._v("#")]),e._v(" Customizing "),o("code",[e._v("")]),e._v(" Authentication")]),e._v(" "),o("p",[e._v("To customize validation, you can implement your own "),o("code",[e._v("Saml2LogoutRequestValidator")]),e._v(".\nAt this point, the validation is minimal, so you may be able to first delegate to the default "),o("code",[e._v("Saml2LogoutRequestValidator")]),e._v(" like so:")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v("@Component\npublic class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator {\n\tprivate final Saml2LogoutRequestValidator delegate = new OpenSamlLogoutRequestValidator();\n\n\t@Override\n public Saml2LogoutRequestValidator logout(Saml2LogoutRequestValidatorParameters parameters) {\n\t\t // verify signature, issuer, destination, and principal name\n\t\tSaml2LogoutValidatorResult result = delegate.authenticate(authentication);\n\n\t\tLogoutRequest logoutRequest = // ... parse using OpenSAML\n // perform custom validation\n }\n}\n")])])]),o("p",[e._v("Then, you can supply your custom "),o("code",[e._v("Saml2LogoutRequestValidator")]),e._v(" in the DSL as follows:")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v("http\n .saml2Logout((saml2) -> saml2\n .logoutRequest((request) -> request\n .logoutRequestAuthenticator(myOpenSamlLogoutRequestAuthenticator)\n )\n );\n")])])]),o("h2",{attrs:{id:"customizing-saml2-logoutresponse-authentication"}},[o("a",{staticClass:"header-anchor",attrs:{href:"#customizing-saml2-logoutresponse-authentication"}},[e._v("#")]),e._v(" Customizing "),o("code",[e._v("")]),e._v(" Authentication")]),e._v(" "),o("p",[e._v("To customize validation, you can implement your own "),o("code",[e._v("Saml2LogoutResponseValidator")]),e._v(".\nAt this point, the validation is minimal, so you may be able to first delegate to the default "),o("code",[e._v("Saml2LogoutResponseValidator")]),e._v(" like so:")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v("@Component\npublic class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidator {\n\tprivate final Saml2LogoutResponseValidator delegate = new OpenSamlLogoutResponseValidator();\n\n\t@Override\n public Saml2LogoutValidatorResult logout(Saml2LogoutResponseValidatorParameters parameters) {\n\t\t// verify signature, issuer, destination, and status\n\t\tSaml2LogoutValidatorResult result = delegate.authenticate(parameters);\n\n\t\tLogoutResponse logoutResponse = // ... parse using OpenSAML\n // perform custom validation\n }\n}\n")])])]),o("p",[e._v("Then, you can supply your custom "),o("code",[e._v("Saml2LogoutResponseValidator")]),e._v(" in the DSL as follows:")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v("http\n .saml2Logout((saml2) -> saml2\n .logoutResponse((response) -> response\n .logoutResponseAuthenticator(myOpenSamlLogoutResponseAuthenticator)\n )\n );\n")])])]),o("h2",{attrs:{id:"customizing-saml2-logoutrequest-storage"}},[o("a",{staticClass:"header-anchor",attrs:{href:"#customizing-saml2-logoutrequest-storage"}},[e._v("#")]),e._v(" Customizing "),o("code",[e._v("")]),e._v(" storage")]),e._v(" "),o("p",[e._v("When your application sends a "),o("code",[e._v("")]),e._v(", the value is stored in the session so that the "),o("code",[e._v("RelayState")]),e._v(" parameter and the "),o("code",[e._v("InResponseTo")]),e._v(" attribute in the "),o("code",[e._v("")]),e._v(" can be verified.")]),e._v(" "),o("p",[e._v("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:")]),e._v(" "),o("div",{staticClass:"language- extra-class"},[o("pre",{pre:!0,attrs:{class:"language-text"}},[o("code",[e._v("http\n .saml2Logout((saml2) -> saml2\n .logoutRequest((request) -> request\n .logoutRequestRepository(myCustomLogoutRequestRepository)\n )\n );\n")])])]),o("p",[o("RouterLink",{attrs:{to:"/en/spring-security/login/authentication.html"}},[e._v("SAML2 Authentication Responses")]),o("RouterLink",{attrs:{to:"/en/spring-security/metadata.html"}},[e._v("SAML2 Metadata")])],1)])}),[],!1,null,null,null);t.default=s.exports}}]);