123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862 |
- [[servlet-saml2login-authenticate-responses]]
- = Authenticating ``<saml2:Response>``s
- To verify SAML 2.0 Responses, Spring Security uses xref:servlet/saml2/login/overview.adoc#servlet-saml2login-authentication-saml2authenticationtokenconverter[`Saml2AuthenticationTokenConverter`] to populate the `Authentication` request and xref:servlet/saml2/login/overview.adoc#servlet-saml2login-architecture[`OpenSaml4AuthenticationProvider`] to authenticate it.
- You can configure this in a number of ways including:
- 1. Changing the way the `RelyingPartyRegistration` is Looked Up
- 2. Setting a clock skew to timestamp validation
- 3. Mapping the response to a list of `GrantedAuthority` instances
- 4. Customizing the strategy for validating assertions
- 5. Customizing the strategy for decrypting response and assertion elements
- To configure these, you'll use the `saml2Login#authenticationManager` method in the DSL.
- [[saml2-response-processing-endpoint]]
- == Changing the SAML Response Processing Endpoint
- The default endpoint is `+/login/saml2/sso/{registrationId}+`.
- You can change this in the DSL and in the associated metadata like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Bean
- SecurityFilterChain securityFilters(HttpSecurity http) throws Exception {
- http
- // ...
- .saml2Login((saml2) -> saml2.loginProcessingUrl("/saml2/login/sso"))
- // ...
- return http.build();
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Bean
- fun securityFilters(val http: HttpSecurity): SecurityFilterChain {
- http {
- // ...
- .saml2Login {
- loginProcessingUrl = "/saml2/login/sso"
- }
- // ...
- }
- return http.build()
- }
- ----
- ======
- and:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml/SSO")
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml/SSO")
- ----
- ======
- [[relyingpartyregistrationresolver-apply]]
- == Changing `RelyingPartyRegistration` lookup
- By default, this converter will match against any associated `<saml2:AuthnRequest>` or any `registrationId` it finds in the URL.
- Or, if it cannot find one in either of those cases, then it attempts to look it up by the `<saml2:Response#Issuer>` element.
- There are a number of circumstances where you might need something more sophisticated, like if you are supporting `ARTIFACT` binding.
- In those cases, you can customize lookup through a custom `AuthenticationConverter`, which you can customize like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Bean
- SecurityFilterChain securityFilters(HttpSecurity http, AuthenticationConverter authenticationConverter) throws Exception {
- http
- // ...
- .saml2Login((saml2) -> saml2.authenticationConverter(authenticationConverter))
- // ...
- return http.build();
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Bean
- fun securityFilters(val http: HttpSecurity, val converter: AuthenticationConverter): SecurityFilterChain {
- http {
- // ...
- .saml2Login {
- authenticationConverter = converter
- }
- // ...
- }
- return http.build()
- }
- ----
- ======
- [[servlet-saml2login-opensamlauthenticationprovider-clockskew]]
- == 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:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableWebSecurity
- public class SecurityConfig {
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
- authenticationProvider.setAssertionValidator(OpenSaml4AuthenticationProvider
- .createDefaultAssertionValidatorWithParameters(assertionToken -> {
- Map<String, Object> 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))
- );
- return http.build();
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- @EnableWebSecurity
- open class SecurityConfig {
- @Bean
- open fun filterChain(http: HttpSecurity): SecurityFilterChain {
- val authenticationProvider = OpenSaml4AuthenticationProvider()
- authenticationProvider.setAssertionValidator(
- OpenSaml4AuthenticationProvider
- .createDefaultAssertionValidatorWithParameters(Converter<OpenSaml4AuthenticationProvider.AssertionToken, ValidationContext> {
- val params: MutableMap<String, Any> = HashMap()
- params[CLOCK_SKEW] =
- Duration.ofMinutes(10).toMillis()
- ValidationContext(params)
- })
- )
- http {
- authorizeRequests {
- authorize(anyRequest, authenticated)
- }
- saml2Login {
- authenticationManager = ProviderManager(authenticationProvider)
- }
- }
- return http.build()
- }
- }
- ----
- ======
- If you are using xref:servlet/saml2/opensaml.adoc[OpenSAML 5], then we have a simpler way, using `OpenSaml5AuthenticationProvider.AssertionValidator`:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableWebSecurity
- public class SecurityConfig {
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- OpenSaml5AuthenticationProvider authenticationProvider = new OpenSaml5AuthenticationProvider();
- AssertionValidator assertionValidator = AssertionValidator.builder()
- .clockSkew(Duration.ofMinutes(10)).build();
- authenticationProvider.setAssertionValidator(assertionValidator);
- http
- .authorizeHttpRequests(authz -> authz
- .anyRequest().authenticated()
- )
- .saml2Login(saml2 -> saml2
- .authenticationManager(new ProviderManager(authenticationProvider))
- );
- return http.build();
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration @EnableWebSecurity
- class SecurityConfig {
- @Bean
- @Throws(Exception::class)
- fun filterChain(http: HttpSecurity): SecurityFilterChain {
- val authenticationProvider = OpenSaml5AuthenticationProvider()
- val assertionValidator = AssertionValidator.builder().clockSkew(Duration.ofMinutes(10)).build()
- authenticationProvider.setAssertionValidator(assertionValidator)
- http {
- authorizeHttpRequests {
- authorize(anyRequest, authenticated)
- }
- saml2Login {
- authenticationManager = ProviderManager(authenticationProvider)
- }
- }
- return http.build()
- }
- }
- ----
- ======
- == Converting an `Assertion` into an `Authentication`
- `OpenSamlXAuthenticationProvider#setResponseAuthenticationConverter` provides a way for you to change how it converts your assertion into an `Authentication` instance.
- You can set a custom converter in the following way:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableWebSecurity
- public class SecurityConfig {
- @Autowired
- Converter<ResponseToken, Saml2Authentication> authenticationConverter;
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- OpenSaml5AuthenticationProvider authenticationProvider = new OpenSaml5AuthenticationProvider();
- authenticationProvider.setResponseAuthenticationConverter(this.authenticationConverter);
- http
- .authorizeHttpRequests((authz) -> authz
- .anyRequest().authenticated())
- .saml2Login((saml2) -> saml2
- .authenticationManager(new ProviderManager(authenticationProvider))
- );
- return http.build();
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- @EnableWebSecurity
- open class SecurityConfig {
- @Autowired
- var authenticationConverter: Converter<ResponseToken, Saml2Authentication>? = null
- @Bean
- open fun filterChain(http: HttpSecurity): SecurityFilterChain {
- val authenticationProvider = OpenSaml5AuthenticationProvider()
- authenticationProvider.setResponseAuthenticationConverter(this.authenticationConverter)
- http {
- authorizeRequests {
- authorize(anyRequest, authenticated)
- }
- saml2Login {
- authenticationManager = ProviderManager(authenticationProvider)
- }
- }
- return http.build()
- }
- }
- ----
- ======
- The ensuing examples all build off of this common construct to show you different ways this converter comes in handy.
- [[servlet-saml2login-opensamlauthenticationprovider-userdetailsservice]]
- == 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:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- class MyUserDetailsResponseAuthenticationConverter implements Converter<ResponseToken, Saml2Authentication> {
- private final ResponseAuthenticationConverter delegate = new ResponseAuthenticationConverter();
- private final UserDetailsService userDetailsService;
- MyUserDetailsResponseAuthenticationConverter(UserDetailsService userDetailsService) {
- this.userDetailsService = userDetailsService;
- }
- @Override
- public Saml2Authentication convert(ResponseToken responseToken) {
- Saml2Authentication authentication = this.delegate.convert(responseToken); <1>
- UserDetails principal = this.userDetailsService.loadByUsername(username); <2>
- String saml2Response = authentication.getSaml2Response();
- Saml2ResponseAssertionAccessor assertion = new OpenSamlResponseAssertionAccessor(
- saml2Response, CollectionUtils.getFirst(response.getAssertions()));
- Collection<GrantedAuthority> authorities = principal.getAuthorities();
- return new Saml2AssertionAuthentication(userDetails, assertion, authorities); <3>
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Component
- open class MyUserDetailsResponseAuthenticationConverter(val delegate: ResponseAuthenticationConverter,
- UserDetailsService userDetailsService): Converter<ResponseToken, Saml2Authentication> {
- @Override
- open fun convert(responseToken: ResponseToken): Saml2Authentication {
- val authentication = this.delegate.convert(responseToken) <1>
- val principal = this.userDetailsService.loadByUsername(username) <2>
- val saml2Response = authentication.getSaml2Response()
- val assertion = OpenSamlResponseAssertionAccessor(
- saml2Response, CollectionUtils.getFirst(response.getAssertions()))
- val authorities = principal.getAuthorities()
- return Saml2AssertionAuthentication(userDetails, assertion, authorities) <3>
- }
- }
- ----
- ======
- <1> First, call the default converter, which extracts attributes and authorities from the response
- <2> Second, call the xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`] using the relevant information
- <3> Third, return an authentication that includes the user details
- [TIP]
- ====
- If your `UserDetailsService` returns a value that also implements `AuthenticatedPrincipal`, then you don't need a custom authentication implementation.
- ====
- Or, if you are using OpenSaml 4, then you can achieve something similar as follows:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableWebSecurity
- public class SecurityConfig {
- @Autowired
- UserDetailsService userDetailsService;
- @Bean
- public SecurityFilterChain filterChain(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))
- );
- return http.build();
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- @EnableWebSecurity
- open class SecurityConfig {
- @Autowired
- var userDetailsService: UserDetailsService? = null
- @Bean
- open fun filterChain(http: HttpSecurity): SecurityFilterChain {
- 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)
- }
- }
- return http.build()
- }
- }
- ----
- ======
- <1> First, call the default converter, which extracts attributes and authorities from the response
- <2> Second, call the xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`] using the relevant information
- <3> Third, return a custom authentication that includes the user details
- [NOTE]
- 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.
- === Configuring the Principal Name
- Sometimes, the principal name is not in the `<saml2:NameID>` element.
- In that case, you can configure the `ResponseAuthenticationConverter` with a custom strategy like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Bean
- ResponseAuthenticationConverter authenticationConverter() {
- ResponseAuthenticationConverter authenticationConverter = new ResponseAuthenticationConverter();
- authenticationConverter.setPrincipalNameConverter((assertion) -> {
- // ... work with OpenSAML's Assertion object to extract the principal
- });
- return authenticationConverter;
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Bean
- fun authenticationConverter(): ResponseAuthenticationConverter {
- val authenticationConverter: ResponseAuthenticationConverter = ResponseAuthenticationConverter()
- authenticationConverter.setPrincipalNameConverter { assertion ->
- // ... work with OpenSAML's Assertion object to extract the principal
- }
- return authenticationConverter
- }
- ----
- ======
- === Configuring a Principal's Granted Authorities
- Spring Security automatically grants `ROLE_USER` when using `OpenSamlXAuhenticationProvider`.
- With `OpenSaml5AuthenticationProvider`, you can configure a different set of granted authorities like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Bean
- ResponseAuthenticationConverter authenticationConverter() {
- ResponseAuthenticationConverter authenticationConverter = new ResponseAuthenticationConverter();
- authenticationConverter.setPrincipalNameConverter((assertion) -> {
- // ... grant the needed authorities based on attributes in the assertion
- });
- return authenticationConverter;
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Bean
- fun authenticationConverter(): ResponseAuthenticationConverter {
- val authenticationConverter = ResponseAuthenticationConverter()
- authenticationConverter.setPrincipalNameConverter{ assertion ->
- // ... grant the needed authorities based on attributes in the assertion
- }
- return authenticationConverter
- }
- ----
- ======
- [[servlet-saml2login-opensamlauthenticationprovider-additionalvalidation]]
- == 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:
- [source,java]
- ----
- 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;
- });
- ----
- When using `OpenSaml5AuthenticationProvider`, you can do the same with less boilerplate:
- [source,java]
- ----
- OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
- ResponseValidator responseValidator = ResponseValidator.withDefaults(myCustomValidator);
- provider.setResponseValidator(responseValidator);
- ----
- You can also customize which validation steps Spring Security should do.
- For example, if you want to skip `Response#InResponseTo` validation, you can call ``ResponseValidator``'s constructor, excluding `InResponseToValidator` from the list:
- [source,java]
- ----
- OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
- ResponseValidator responseValidator = new ResponseValidator(new DestinationValidator(), new IssuerValidator());
- provider.setResponseValidator(responseValidator);
- ----
- [TIP]
- ====
- OpenSAML performs `Asssertion#InResponseTo` validation in its `BearerSubjectConfirmationValidator` class, which is configurable using <<_performing_additional_assertion_validation, setAssertionValidator>>.
- ====
- == Performing Additional Assertion Validation
- `OpenSaml4AuthenticationProvider` performs minimal validation on SAML 2.0 Assertions.
- After verifying the signature, it will:
- 1. Validate `<AudienceRestriction>` and `<DelegationRestriction>` conditions
- 2. Validate ``<SubjectConfirmation>``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.
- [[servlet-saml2login-opensamlauthenticationprovider-onetimeuse]]
- For example, you can use OpenSAML's `OneTimeUseConditionValidator` to also validate a `<OneTimeUse>` condition, like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- 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::
- +
- [source,kotlin,role="secondary"]
- ----
- 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) {
- return@setAssertionValidator result
- }
- } catch (e: Exception) {
- return@setAssertionValidator result.concat(Saml2Error(INVALID_ASSERTION, e.message))
- }
- result.concat(Saml2Error(INVALID_ASSERTION, context.validationFailureMessage))
- }
- ----
- ======
- [NOTE]
- 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 `<AudienceRestriction>` or the `<SubjectConfirmation>` since you are doing those yourself.
- If you are using xref:servlet/saml2/opensaml.adoc[OpenSAML 5], then we have a simpler way using `OpenSaml5AuthenticationProvider.AssertionValidator`:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
- OneTimeUseConditionValidator validator = ...;
- AssertionValidator assertionValidator = AssertionValidator.builder()
- .conditionValidators((c) -> c.add(validator)).build();
- provider.setAssertionValidator(assertionValidator);
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- val provider = OpenSaml5AuthenticationProvider()
- val validator: OneTimeUseConditionValidator = ...;
- val assertionValidator = AssertionValidator.builder()
- .conditionValidators { add(validator) }.build()
- provider.setAssertionValidator(assertionValidator)
- ----
- ======
- You can use this same builder to remove validators that you don't want to use like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
- AssertionValidator assertionValidator = AssertionValidator.builder()
- .conditionValidators((c) -> c.removeIf(AudienceRestrictionValidator.class::isInstance)).build();
- provider.setAssertionValidator(assertionValidator);
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- val provider = new OpenSaml5AuthenticationProvider()
- val assertionValidator = AssertionValidator.builder()
- .conditionValidators {
- c: List<ConditionValidator> -> c.removeIf { it is AudienceRestrictionValidator }
- }.build()
- provider.setAssertionValidator(assertionValidator)
- ----
- ======
- [[servlet-saml2login-opensamlauthenticationprovider-decryption]]
- == Customizing Decryption
- Spring Security decrypts `<saml2:EncryptedAssertion>`, `<saml2:EncryptedAttribute>`, and `<saml2:EncryptedID>` elements automatically by using the decryption xref:servlet/saml2/login/overview.adoc#servlet-saml2login-rpr-credentials[`Saml2X509Credential` instances] registered in the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`].
- `OpenSaml4AuthenticationProvider` exposes xref:servlet/saml2/login/overview.adoc#servlet-saml2login-architecture[two decryption strategies].
- The response decrypter is for decrypting encrypted elements of the `<saml2:Response>`, like `<saml2:EncryptedAssertion>`.
- The assertion decrypter is for decrypting encrypted elements of the `<saml2:Assertion>`, like `<saml2:EncryptedAttribute>` and `<saml2:EncryptedID>`.
- 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 `<saml2:Response>`, you can use it instead like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- MyDecryptionService decryptionService = ...;
- OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
- provider.setResponseElementsDecrypter((responseToken) -> decryptionService.decrypt(responseToken.getResponse()));
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- val decryptionService: MyDecryptionService = ...
- val provider = OpenSaml4AuthenticationProvider()
- provider.setResponseElementsDecrypter { responseToken -> decryptionService.decrypt(responseToken.response) }
- ----
- ======
- If you are also decrypting individual elements in a `<saml2:Assertion>`, you can customize the assertion decrypter, too:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- provider.setAssertionElementsDecrypter((assertionToken) -> decryptionService.decrypt(assertionToken.getAssertion()));
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- provider.setAssertionElementsDecrypter { assertionToken -> decryptionService.decrypt(assertionToken.assertion) }
- ----
- ======
- NOTE: 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.
- [[servlet-saml2login-authenticationmanager-custom]]
- == Using a Custom Authentication Manager
- [[servlet-saml2login-opensamlauthenticationprovider-authenticationmanager]]
- 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.
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableWebSecurity
- public class SecurityConfig {
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- AuthenticationManager authenticationManager = new MySaml2AuthenticationManager(...);
- http
- .authorizeHttpRequests(authorize -> authorize
- .anyRequest().authenticated()
- )
- .saml2Login(saml2 -> saml2
- .authenticationManager(authenticationManager)
- )
- ;
- return http.build();
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- @EnableWebSecurity
- open class SecurityConfig {
- @Bean
- open fun filterChain(http: HttpSecurity): SecurityFilterChain {
- val customAuthenticationManager: AuthenticationManager = MySaml2AuthenticationManager(...)
- http {
- authorizeRequests {
- authorize(anyRequest, authenticated)
- }
- saml2Login {
- authenticationManager = customAuthenticationManager
- }
- }
- return http.build()
- }
- }
- ----
- ======
- [[servlet-saml2login-authenticatedprincipal]]
- == 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:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @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::
- +
- [source,kotlin,role="secondary"]
- ----
- @Controller
- class MainController {
- @GetMapping("/")
- fun index(@AuthenticationPrincipal principal: Saml2AuthenticatedPrincipal, model: Model): String {
- val email = principal.getFirstAttribute<String>("email")
- model.setAttribute("email", email)
- return "index"
- }
- }
- ----
- ======
- [TIP]
- 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.
|