# Authenticating ``s To verify SAML 2.0 Responses, Spring Security uses [`OpenSaml4AuthenticationProvider`](overview.html#servlet-saml2login-architecture) by default. You can configure this in a number of ways including: 1. Setting a clock skew to timestamp validation 2. Mapping the response to a list of `GrantedAuthority` instances 3. Customizing the strategy for validating assertions 4. Customizing the strategy for decrypting response and assertion elements To configure these, you’ll use the `saml2Login#authenticationManager` method in the DSL. ## Setting a Clock Skew It’s not uncommon for the asserting and relying parties to have system clocks that aren’t perfectly synchronized. For that reason, you can configure `OpenSaml4AuthenticationProvider` 's default assertion validator with some tolerance: Java ``` @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider(); authenticationProvider.setAssertionValidator(OpenSaml4AuthenticationProvider .createDefaultAssertionValidator(assertionToken -> { Map params = new HashMap<>(); params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis()); // ... other validation parameters return new ValidationContext(params); }) ); http .authorizeHttpRequests(authz -> authz .anyRequest().authenticated() ) .saml2Login(saml2 -> saml2 .authenticationManager(new ProviderManager(authenticationProvider)) ); } } ``` Kotlin ``` @EnableWebSecurity open class SecurityConfig : WebSecurityConfigurerAdapter() { override fun configure(http: HttpSecurity) { val authenticationProvider = OpenSaml4AuthenticationProvider() authenticationProvider.setAssertionValidator( OpenSaml4AuthenticationProvider .createDefaultAssertionValidator(Converter { val params: MutableMap = HashMap() params[CLOCK_SKEW] = Duration.ofMinutes(10).toMillis() ValidationContext(params) }) ) http { authorizeRequests { authorize(anyRequest, authenticated) } saml2Login { authenticationManager = ProviderManager(authenticationProvider) } } } } ``` ## Coordinating with a `UserDetailsService` Or, perhaps you would like to include user details from a legacy `UserDetailsService`. In that case, the response authentication converter can come in handy, as can be seen below: Java ``` @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider(); authenticationProvider.setResponseAuthenticationConverter(responseToken -> { Saml2Authentication authentication = OpenSaml4AuthenticationProvider .createDefaultResponseAuthenticationConverter() (1) .convert(responseToken); Assertion assertion = responseToken.getResponse().getAssertions().get(0); String username = assertion.getSubject().getNameID().getValue(); UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); (2) return MySaml2Authentication(userDetails, authentication); (3) }); http .authorizeHttpRequests(authz -> authz .anyRequest().authenticated() ) .saml2Login(saml2 -> saml2 .authenticationManager(new ProviderManager(authenticationProvider)) ); } } ``` Kotlin ``` @EnableWebSecurity open class SecurityConfig : WebSecurityConfigurerAdapter() { @Autowired var userDetailsService: UserDetailsService? = null override fun configure(http: HttpSecurity) { val authenticationProvider = OpenSaml4AuthenticationProvider() authenticationProvider.setResponseAuthenticationConverter { responseToken: OpenSaml4AuthenticationProvider.ResponseToken -> val authentication = OpenSaml4AuthenticationProvider .createDefaultResponseAuthenticationConverter() (1) .convert(responseToken) val assertion: Assertion = responseToken.response.assertions[0] val username: String = assertion.subject.nameID.value val userDetails = userDetailsService!!.loadUserByUsername(username) (2) MySaml2Authentication(userDetails, authentication) (3) } http { authorizeRequests { authorize(anyRequest, authenticated) } saml2Login { authenticationManager = ProviderManager(authenticationProvider) } } } } ``` |**1**| First, call the default converter, which extracts attributes and authorities from the response | |-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |**2**|Second, call the [`UserDetailsService`](../../authentication/passwords/user-details-service.html#servlet-authentication-userdetailsservice) using the relevant information| |**3**| Third, return a custom authentication that includes the user details | | |It’s not required to call `OpenSaml4AuthenticationProvider` 's default authentication converter.
It returns a `Saml2AuthenticatedPrincipal` containing the attributes it extracted from `AttributeStatement`s as well as the single `ROLE_USER` authority.| |---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ## Performing Additional Response Validation `OpenSaml4AuthenticationProvider` validates the `Issuer` and `Destination` values right after decrypting the `Response`. You can customize the validation by extending the default validator concatenating with your own response validator, or you can replace it entirely with yours. For example, you can throw a custom exception with any additional information available in the `Response` object, like so: ``` OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); provider.setResponseValidator((responseToken) -> { Saml2ResponseValidatorResult result = OpenSamlAuthenticationProvider .createDefaultResponseValidator() .convert(responseToken) .concat(myCustomValidator.convert(responseToken)); if (!result.getErrors().isEmpty()) { String inResponseTo = responseToken.getInResponseTo(); throw new CustomSaml2AuthenticationException(result, inResponseTo); } return result; }); ``` ## Performing Additional Assertion Validation `OpenSaml4AuthenticationProvider` performs minimal validation on SAML 2.0 Assertions. After verifying the signature, it will: 1. Validate `` and `` conditions 2. Validate ``s, expect for any IP address information To perform additional validation, you can configure your own assertion validator that delegates to `OpenSaml4AuthenticationProvider` 's default and then performs its own. For example, you can use OpenSAML’s `OneTimeUseConditionValidator` to also validate a `` condition, like so: Java ``` OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); OneTimeUseConditionValidator validator = ...; provider.setAssertionValidator(assertionToken -> { Saml2ResponseValidatorResult result = OpenSaml4AuthenticationProvider .createDefaultAssertionValidator() .convert(assertionToken); Assertion assertion = assertionToken.getAssertion(); OneTimeUse oneTimeUse = assertion.getConditions().getOneTimeUse(); ValidationContext context = new ValidationContext(); try { if (validator.validate(oneTimeUse, assertion, context) = ValidationResult.VALID) { return result; } } catch (Exception e) { return result.concat(new Saml2Error(INVALID_ASSERTION, e.getMessage())); } return result.concat(new Saml2Error(INVALID_ASSERTION, context.getValidationFailureMessage())); }); ``` Kotlin ``` var provider = OpenSaml4AuthenticationProvider() var validator: OneTimeUseConditionValidator = ... provider.setAssertionValidator { assertionToken -> val result = OpenSaml4AuthenticationProvider .createDefaultAssertionValidator() .convert(assertionToken) val assertion: Assertion = assertionToken.assertion val oneTimeUse: OneTimeUse = assertion.conditions.oneTimeUse val context = ValidationContext() try { if (validator.validate(oneTimeUse, assertion, context) = ValidationResult.VALID) { [email protected] result } } catch (e: Exception) { [email protected] result.concat(Saml2Error(INVALID_ASSERTION, e.message)) } result.concat(Saml2Error(INVALID_ASSERTION, context.validationFailureMessage)) } ``` | |While recommended, it’s not necessary to call `OpenSaml4AuthenticationProvider` 's default assertion validator.
A circumstance where you would skip it would be if you don’t need it to check the `` or the `` since you are doing those yourself.| |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ## Customizing Decryption Spring Security decrypts ``, ``, and `` elements automatically by using the decryption [`Saml2X509Credential` instances](overview.html#servlet-saml2login-rpr-credentials) registered in the [`RelyingPartyRegistration`](overview.html#servlet-saml2login-relyingpartyregistration). `OpenSaml4AuthenticationProvider` exposes [two decryption strategies](overview.html#servlet-saml2login-architecture). The response decrypter is for decrypting encrypted elements of the ``, like ``. The assertion decrypter is for decrypting encrypted elements of the ``, like `` and ``. You can replace `OpenSaml4AuthenticationProvider’s default decryption strategy with your own. For example, if you have a separate service that decrypts the assertions in a ``, you can use it instead like so: Java ``` MyDecryptionService decryptionService = ...; OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); provider.setResponseElementsDecrypter((responseToken) -> decryptionService.decrypt(responseToken.getResponse())); ``` Kotlin ``` val decryptionService: MyDecryptionService = ... val provider = OpenSaml4AuthenticationProvider() provider.setResponseElementsDecrypter { responseToken -> decryptionService.decrypt(responseToken.response) } ``` If you are also decrypting individual elements in a ``, you can customize the assertion decrypter, too: Java ``` provider.setAssertionElementsDecrypter((assertionToken) -> decryptionService.decrypt(assertionToken.getAssertion())); ``` Kotlin ``` provider.setAssertionElementsDecrypter { assertionToken -> decryptionService.decrypt(assertionToken.assertion) } ``` | |There are two separate decrypters since assertions can be signed separately from responses.
Trying to decrypt a signed assertion’s elements before signature verification may invalidate the signature.
If your asserting party signs the response only, then it’s safe to decrypt all elements using only the response decrypter.| |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ## Using a Custom Authentication Manager Of course, the `authenticationManager` DSL method can be also used to perform a completely custom SAML 2.0 authentication. This authentication manager should expect a `Saml2AuthenticationToken` object containing the SAML 2.0 Response XML data. Java ``` @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { AuthenticationManager authenticationManager = new MySaml2AuthenticationManager(...); http .authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated() ) .saml2Login(saml2 -> saml2 .authenticationManager(authenticationManager) ) ; } } ``` Kotlin ``` @EnableWebSecurity open class SecurityConfig : WebSecurityConfigurerAdapter() { override fun configure(http: HttpSecurity) { val customAuthenticationManager: AuthenticationManager = MySaml2AuthenticationManager(...) http { authorizeRequests { authorize(anyRequest, authenticated) } saml2Login { authenticationManager = customAuthenticationManager } } } } ``` ## Using `Saml2AuthenticatedPrincipal` With the relying party correctly configured for a given asserting party, it’s ready to accept assertions. Once the relying party validates an assertion, the result is a `Saml2Authentication` with a `Saml2AuthenticatedPrincipal`. This means that you can access the principal in your controller like so: Java ``` @Controller public class MainController { @GetMapping("/") public String index(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal, Model model) { String email = principal.getFirstAttribute("email"); model.setAttribute("email", email); return "index"; } } ``` Kotlin ``` @Controller class MainController { @GetMapping("/") fun index(@AuthenticationPrincipal principal: Saml2AuthenticatedPrincipal, model: Model): String { val email = principal.getFirstAttribute("email") model.setAttribute("email", email) return "index" } } ``` | |Because the SAML 2.0 specification allows for each attribute to have multiple values, you can either call `getAttribute` to get the list of attributes or `getFirstAttribute` to get the first in the list.`getFirstAttribute` is quite handy when you know that there is only one value.| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| [SAML2 Authentication Requests](authentication-requests.html)[SAML2 Logout](../logout.html)