You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
663 lines
22 KiB
663 lines
22 KiB
[[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() |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
[[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"] |
|
---- |
|
@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. |
|
|
|
[[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.
|
|
|