296.1090c539.js 21.0 KB
Newer Older
茶陵後's avatar
茶陵後 已提交
1
(window.webpackJsonp=window.webpackJsonp||[]).push([[296],{722:function(e,t,n){"use strict";n.r(t);var a=n(56),i=Object(a.a)({},(function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[n("h1",{attrs:{id:"authenticating-saml2-response-s"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#authenticating-saml2-response-s"}},[e._v("#")]),e._v(" Authenticating "),n("code",[e._v("<saml2:Response>")]),e._v("s")]),e._v(" "),n("p",[e._v("To verify SAML 2.0 Responses, Spring Security uses "),n("RouterLink",{attrs:{to:"/en/spring-security/overview.html#servlet-saml2login-architecture"}},[n("code",[e._v("OpenSaml4AuthenticationProvider")])]),e._v(" by default.")],1),e._v(" "),n("p",[e._v("You can configure this in a number of ways including:")]),e._v(" "),n("ol",[n("li",[n("p",[e._v("Setting a clock skew to timestamp validation")])]),e._v(" "),n("li",[n("p",[e._v("Mapping the response to a list of "),n("code",[e._v("GrantedAuthority")]),e._v(" instances")])]),e._v(" "),n("li",[n("p",[e._v("Customizing the strategy for validating assertions")])]),e._v(" "),n("li",[n("p",[e._v("Customizing the strategy for decrypting response and assertion elements")])])]),e._v(" "),n("p",[e._v("To configure these, you’ll use the "),n("code",[e._v("saml2Login#authenticationManager")]),e._v(" method in the DSL.")]),e._v(" "),n("h2",{attrs:{id:"setting-a-clock-skew"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#setting-a-clock-skew"}},[e._v("#")]),e._v(" Setting a Clock Skew")]),e._v(" "),n("p",[e._v("It’s not uncommon for the asserting and relying parties to have system clocks that aren’t perfectly synchronized.\nFor that reason, you can configure "),n("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v(" 's default assertion validator with some tolerance:")]),e._v(" "),n("p",[e._v("Java")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("@EnableWebSecurity\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();\n        authenticationProvider.setAssertionValidator(OpenSaml4AuthenticationProvider\n                .createDefaultAssertionValidator(assertionToken -> {\n                    Map<String, Object> params = new HashMap<>();\n                    params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis());\n                    // ... other validation parameters\n                    return new ValidationContext(params);\n                })\n        );\n\n        http\n            .authorizeHttpRequests(authz -> authz\n                .anyRequest().authenticated()\n            )\n            .saml2Login(saml2 -> saml2\n                .authenticationManager(new ProviderManager(authenticationProvider))\n            );\n    }\n}\n")])])]),n("p",[e._v("Kotlin")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("@EnableWebSecurity\nopen class SecurityConfig : WebSecurityConfigurerAdapter() {\n    override fun configure(http: HttpSecurity) {\n        val authenticationProvider = OpenSaml4AuthenticationProvider()\n        authenticationProvider.setAssertionValidator(\n            OpenSaml4AuthenticationProvider\n                .createDefaultAssertionValidator(Converter<OpenSaml4AuthenticationProvider.AssertionToken, ValidationContext> {\n                    val params: MutableMap<String, Any> = HashMap()\n                    params[CLOCK_SKEW] =\n                        Duration.ofMinutes(10).toMillis()\n                    ValidationContext(params)\n                })\n        )\n        http {\n            authorizeRequests {\n                authorize(anyRequest, authenticated)\n            }\n            saml2Login {\n                authenticationManager = ProviderManager(authenticationProvider)\n            }\n        }\n    }\n}\n")])])]),n("h2",{attrs:{id:"coordinating-with-a-userdetailsservice"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#coordinating-with-a-userdetailsservice"}},[e._v("#")]),e._v(" Coordinating with a "),n("code",[e._v("UserDetailsService")])]),e._v(" "),n("p",[e._v("Or, perhaps you would like to include user details from a legacy "),n("code",[e._v("UserDetailsService")]),e._v(".\nIn that case, the response authentication converter can come in handy, as can be seen below:")]),e._v(" "),n("p",[e._v("Java")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("@EnableWebSecurity\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n    @Autowired\n    UserDetailsService userDetailsService;\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();\n        authenticationProvider.setResponseAuthenticationConverter(responseToken -> {\n            Saml2Authentication authentication = OpenSaml4AuthenticationProvider\n                    .createDefaultResponseAuthenticationConverter() (1)\n                    .convert(responseToken);\n            Assertion assertion = responseToken.getResponse().getAssertions().get(0);\n            String username = assertion.getSubject().getNameID().getValue();\n            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); (2)\n            return MySaml2Authentication(userDetails, authentication); (3)\n        });\n\n        http\n            .authorizeHttpRequests(authz -> authz\n                .anyRequest().authenticated()\n            )\n            .saml2Login(saml2 -> saml2\n                .authenticationManager(new ProviderManager(authenticationProvider))\n            );\n    }\n}\n")])])]),n("p",[e._v("Kotlin")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("@EnableWebSecurity\nopen class SecurityConfig : WebSecurityConfigurerAdapter() {\n    @Autowired\n    var userDetailsService: UserDetailsService? = null\n\n    override fun configure(http: HttpSecurity) {\n        val authenticationProvider = OpenSaml4AuthenticationProvider()\n        authenticationProvider.setResponseAuthenticationConverter { responseToken: OpenSaml4AuthenticationProvider.ResponseToken ->\n            val authentication = OpenSaml4AuthenticationProvider\n                .createDefaultResponseAuthenticationConverter() (1)\n                .convert(responseToken)\n            val assertion: Assertion = responseToken.response.assertions[0]\n            val username: String = assertion.subject.nameID.value\n            val userDetails = userDetailsService!!.loadUserByUsername(username) (2)\n            MySaml2Authentication(userDetails, authentication) (3)\n        }\n        http {\n            authorizeRequests {\n                authorize(anyRequest, authenticated)\n            }\n            saml2Login {\n                authenticationManager = ProviderManager(authenticationProvider)\n            }\n        }\n    }\n}\n")])])]),n("table",[n("thead",[n("tr",[n("th",[n("strong",[e._v("1")])]),e._v(" "),n("th",[e._v("First, call the default converter, which extracts attributes and authorities from the response")])])]),e._v(" "),n("tbody",[n("tr",[n("td",[n("strong",[e._v("2")])]),e._v(" "),n("td",[e._v("Second, call the "),n("RouterLink",{attrs:{to:"/authentication/passwords/user-details-service.html#servlet-authentication-userdetailsservice"}},[n("code",[e._v("UserDetailsService")])]),e._v(" using the relevant information")],1)]),e._v(" "),n("tr",[n("td",[n("strong",[e._v("3")])]),e._v(" "),n("td",[e._v("Third, return a custom authentication that includes the user details")])])])]),e._v(" "),n("table",[n("thead",[n("tr",[n("th"),e._v(" "),n("th",[e._v("It’s not required to call "),n("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v(" 's default authentication converter."),n("br"),e._v("It returns a "),n("code",[e._v("Saml2AuthenticatedPrincipal")]),e._v(" containing the attributes it extracted from "),n("code",[e._v("AttributeStatement")]),e._v("s as well as the single "),n("code",[e._v("ROLE_USER")]),e._v(" authority.")])])]),e._v(" "),n("tbody")]),e._v(" "),n("h2",{attrs:{id:"performing-additional-response-validation"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#performing-additional-response-validation"}},[e._v("#")]),e._v(" Performing Additional Response Validation")]),e._v(" "),n("p",[n("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v(" validates the "),n("code",[e._v("Issuer")]),e._v(" and "),n("code",[e._v("Destination")]),e._v(" values right after decrypting the "),n("code",[e._v("Response")]),e._v(".\nYou can customize the validation by extending the default validator concatenating with your own response validator, or you can replace it entirely with yours.")]),e._v(" "),n("p",[e._v("For example, you can throw a custom exception with any additional information available in the "),n("code",[e._v("Response")]),e._v(" object, like so:")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();\nprovider.setResponseValidator((responseToken) -> {\n\tSaml2ResponseValidatorResult result = OpenSamlAuthenticationProvider\n\t\t.createDefaultResponseValidator()\n\t\t.convert(responseToken)\n\t\t.concat(myCustomValidator.convert(responseToken));\n\tif (!result.getErrors().isEmpty()) {\n\t\tString inResponseTo = responseToken.getInResponseTo();\n\t\tthrow new CustomSaml2AuthenticationException(result, inResponseTo);\n\t}\n\treturn result;\n});\n")])])]),n("h2",{attrs:{id:"performing-additional-assertion-validation"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#performing-additional-assertion-validation"}},[e._v("#")]),e._v(" Performing Additional Assertion Validation")]),e._v(" "),n("p",[n("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v(" performs minimal validation on SAML 2.0 Assertions.\nAfter verifying the signature, it will:")]),e._v(" "),n("ol",[n("li",[n("p",[e._v("Validate "),n("code",[e._v("<AudienceRestriction>")]),e._v(" and "),n("code",[e._v("<DelegationRestriction>")]),e._v(" conditions")])]),e._v(" "),n("li",[n("p",[e._v("Validate "),n("code",[e._v("<SubjectConfirmation>")]),e._v("s, expect for any IP address information")])])]),e._v(" "),n("p",[e._v("To perform additional validation, you can configure your own assertion validator that delegates to "),n("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v(" 's default and then performs its own.")]),e._v(" "),n("p",[e._v("For example, you can use OpenSAML’s "),n("code",[e._v("OneTimeUseConditionValidator")]),e._v(" to also validate a "),n("code",[e._v("<OneTimeUse>")]),e._v(" condition, like so:")]),e._v(" "),n("p",[e._v("Java")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();\nOneTimeUseConditionValidator validator = ...;\nprovider.setAssertionValidator(assertionToken -> {\n    Saml2ResponseValidatorResult result = OpenSaml4AuthenticationProvider\n            .createDefaultAssertionValidator()\n            .convert(assertionToken);\n    Assertion assertion = assertionToken.getAssertion();\n    OneTimeUse oneTimeUse = assertion.getConditions().getOneTimeUse();\n    ValidationContext context = new ValidationContext();\n    try {\n        if (validator.validate(oneTimeUse, assertion, context) = ValidationResult.VALID) {\n            return result;\n        }\n    } catch (Exception e) {\n        return result.concat(new Saml2Error(INVALID_ASSERTION, e.getMessage()));\n    }\n    return result.concat(new Saml2Error(INVALID_ASSERTION, context.getValidationFailureMessage()));\n});\n")])])]),n("p",[e._v("Kotlin")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("var provider = OpenSaml4AuthenticationProvider()\nvar validator: OneTimeUseConditionValidator = ...\nprovider.setAssertionValidator { assertionToken ->\n    val result = OpenSaml4AuthenticationProvider\n        .createDefaultAssertionValidator()\n        .convert(assertionToken)\n    val assertion: Assertion = assertionToken.assertion\n    val oneTimeUse: OneTimeUse = assertion.conditions.oneTimeUse\n    val context = ValidationContext()\n    try {\n        if (validator.validate(oneTimeUse, assertion, context) = ValidationResult.VALID) {\n            [email protected] result\n        }\n    } catch (e: Exception) {\n        [email protected] result.concat(Saml2Error(INVALID_ASSERTION, e.message))\n    }\n    result.concat(Saml2Error(INVALID_ASSERTION, context.validationFailureMessage))\n}\n")])])]),n("table",[n("thead",[n("tr",[n("th"),e._v(" "),n("th",[e._v("While recommended, it’s not necessary to call "),n("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v(" 's default assertion validator."),n("br"),e._v("A circumstance where you would skip it would be if you don’t need it to check the "),n("code",[e._v("<AudienceRestriction>")]),e._v(" or the "),n("code",[e._v("<SubjectConfirmation>")]),e._v(" since you are doing those yourself.")])])]),e._v(" "),n("tbody")]),e._v(" "),n("h2",{attrs:{id:"customizing-decryption"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#customizing-decryption"}},[e._v("#")]),e._v(" Customizing Decryption")]),e._v(" "),n("p",[e._v("Spring Security decrypts "),n("code",[e._v("<saml2:EncryptedAssertion>")]),e._v(", "),n("code",[e._v("<saml2:EncryptedAttribute>")]),e._v(", and "),n("code",[e._v("<saml2:EncryptedID>")]),e._v(" elements automatically by using the decryption "),n("RouterLink",{attrs:{to:"/en/spring-security/overview.html#servlet-saml2login-rpr-credentials"}},[n("code",[e._v("Saml2X509Credential")]),e._v(" instances")]),e._v(" registered in the "),n("RouterLink",{attrs:{to:"/en/spring-security/overview.html#servlet-saml2login-relyingpartyregistration"}},[n("code",[e._v("RelyingPartyRegistration")])]),e._v(".")],1),e._v(" "),n("p",[n("code",[e._v("OpenSaml4AuthenticationProvider")]),e._v(" exposes "),n("RouterLink",{attrs:{to:"/en/spring-security/overview.html#servlet-saml2login-architecture"}},[e._v("two decryption strategies")]),e._v(".\nThe response decrypter is for decrypting encrypted elements of the "),n("code",[e._v("<saml2:Response>")]),e._v(", like "),n("code",[e._v("<saml2:EncryptedAssertion>")]),e._v(".\nThe assertion decrypter is for decrypting encrypted elements of the "),n("code",[e._v("<saml2:Assertion>")]),e._v(", like "),n("code",[e._v("<saml2:EncryptedAttribute>")]),e._v(" and "),n("code",[e._v("<saml2:EncryptedID>")]),e._v(".")],1),e._v(" "),n("p",[e._v("You can replace "),n("code",[e._v("OpenSaml4AuthenticationProvider’s default decryption strategy with your own. For example, if you have a separate service that decrypts the assertions in a")]),n("a",{attrs:{href:"saml2:Response"}},[e._v("saml2:Response")]),e._v("`, you can use it instead like so:")]),e._v(" "),n("p",[e._v("Java")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("MyDecryptionService decryptionService = ...;\nOpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();\nprovider.setResponseElementsDecrypter((responseToken) -> decryptionService.decrypt(responseToken.getResponse()));\n")])])]),n("p",[e._v("Kotlin")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("val decryptionService: MyDecryptionService = ...\nval provider = OpenSaml4AuthenticationProvider()\nprovider.setResponseElementsDecrypter { responseToken -> decryptionService.decrypt(responseToken.response) }\n")])])]),n("p",[e._v("If you are also decrypting individual elements in a "),n("code",[e._v("<saml2:Assertion>")]),e._v(", you can customize the assertion decrypter, too:")]),e._v(" "),n("p",[e._v("Java")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("provider.setAssertionElementsDecrypter((assertionToken) -> decryptionService.decrypt(assertionToken.getAssertion()));\n")])])]),n("p",[e._v("Kotlin")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("provider.setAssertionElementsDecrypter { assertionToken -> decryptionService.decrypt(assertionToken.assertion) }\n")])])]),n("table",[n("thead",[n("tr",[n("th"),e._v(" "),n("th",[e._v("There are two separate decrypters since assertions can be signed separately from responses."),n("br"),e._v("Trying to decrypt a signed assertion’s elements before signature verification may invalidate the signature."),n("br"),e._v("If your asserting party signs the response only, then it’s safe to decrypt all elements using only the response decrypter.")])])]),e._v(" "),n("tbody")]),e._v(" "),n("h2",{attrs:{id:"using-a-custom-authentication-manager"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#using-a-custom-authentication-manager"}},[e._v("#")]),e._v(" Using a Custom Authentication Manager")]),e._v(" "),n("p",[e._v("Of course, the "),n("code",[e._v("authenticationManager")]),e._v(" DSL method can be also used to perform a completely custom SAML 2.0 authentication.\nThis authentication manager should expect a "),n("code",[e._v("Saml2AuthenticationToken")]),e._v(" object containing the SAML 2.0 Response XML data.")]),e._v(" "),n("p",[e._v("Java")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("@EnableWebSecurity\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n        AuthenticationManager authenticationManager = new MySaml2AuthenticationManager(...);\n        http\n            .authorizeHttpRequests(authorize -> authorize\n                .anyRequest().authenticated()\n            )\n            .saml2Login(saml2 -> saml2\n                .authenticationManager(authenticationManager)\n            )\n        ;\n    }\n}\n")])])]),n("p",[e._v("Kotlin")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v("@EnableWebSecurity\nopen class SecurityConfig : WebSecurityConfigurerAdapter() {\n    override fun configure(http: HttpSecurity) {\n        val customAuthenticationManager: AuthenticationManager = MySaml2AuthenticationManager(...)\n        http {\n            authorizeRequests {\n                authorize(anyRequest, authenticated)\n            }\n            saml2Login {\n                authenticationManager = customAuthenticationManager\n            }\n        }\n    }\n}\n")])])]),n("h2",{attrs:{id:"using-saml2authenticatedprincipal"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#using-saml2authenticatedprincipal"}},[e._v("#")]),e._v(" Using "),n("code",[e._v("Saml2AuthenticatedPrincipal")])]),e._v(" "),n("p",[e._v("With the relying party correctly configured for a given asserting party, it’s ready to accept assertions.\nOnce the relying party validates an assertion, the result is a "),n("code",[e._v("Saml2Authentication")]),e._v(" with a "),n("code",[e._v("Saml2AuthenticatedPrincipal")]),e._v(".")]),e._v(" "),n("p",[e._v("This means that you can access the principal in your controller like so:")]),e._v(" "),n("p",[e._v("Java")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v('@Controller\npublic class MainController {\n\t@GetMapping("/")\n\tpublic String index(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal, Model model) {\n\t\tString email = principal.getFirstAttribute("email");\n\t\tmodel.setAttribute("email", email);\n\t\treturn "index";\n\t}\n}\n')])])]),n("p",[e._v("Kotlin")]),e._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[e._v('@Controller\nclass MainController {\n    @GetMapping("/")\n    fun index(@AuthenticationPrincipal principal: Saml2AuthenticatedPrincipal, model: Model): String {\n        val email = principal.getFirstAttribute<String>("email")\n        model.setAttribute("email", email)\n        return "index"\n    }\n}\n')])])]),n("table",[n("thead",[n("tr",[n("th"),e._v(" "),n("th",[e._v("Because the SAML 2.0 specification allows for each attribute to have multiple values, you can either call "),n("code",[e._v("getAttribute")]),e._v(" to get the list of attributes or "),n("code",[e._v("getFirstAttribute")]),e._v(" to get the first in the list."),n("code",[e._v("getFirstAttribute")]),e._v(" is quite handy when you know that there is only one value.")])])]),e._v(" "),n("tbody")]),e._v(" "),n("p",[n("RouterLink",{attrs:{to:"/en/spring-security/authentication-requests.html"}},[e._v("SAML2 Authentication Requests")]),n("RouterLink",{attrs:{to:"/en/logout.html"}},[e._v("SAML2 Logout")])],1)])}),[],!1,null,null,null);t.default=i.exports}}]);