There are various ways to hook into the SAML2 authentication process in Spring Boot, but the most common and seemingly recommended one is to customize the OpenSamlAuthenticationProvider
that Spring Security uses for authentication.
The method has undergone a lot of changes in recent versions however. The examples below are in Kotlin and you have to translate it to Java (if that's what you want) yourself. Also, which Spring Security version that is used in which Spring Boot version, you also have to find out for yourself, but version 2.5.6 definitely puts you in the last case below.
Spring Security < 5.3.0
The setAuthoritiesExtractor
makes it easy to just add a method that extracts authorities / roles from the assertion. The method should return an object of type Collection<GrantedAuthority>
.
.saml2Login()
.authenticationManager(
ProviderManager(
OpenSamlAuthenticationProvider().apply {
setAuthoritiesExtractor { a: Assertion? ->
// return a list of authorities based on the assertion
}
}
)
)
Spring Security >= 5.3.0 and < 5.4.0
setAuthoritiesExtractor
got deprecated with the recommendation to use setResponseAuthenticationConverter
instead. This is unfortunately more elaborate than the previous method.
.saml2Login()
.authenticationManager(
ProviderManager(
OpenSamlAuthenticationProvider().apply {
setResponseAuthenticationConverter {
val defaultAuth = OpenSamlAuthenticationProvider.createDefaultResponseAuthenticationConverter().convert(it);
if (defaultAuth !== null && defaultAuth.isAuthenticated) {
val authoritiesLists = it.response.assertions.map { ass ->
// return a list of authorities based on the assertion
}
val authoritiesList = authoritiesLists.flatten().toSet()
Saml2Authentication(defaultAuth.principal as AuthenticatedPrincipal, defaultAuth.saml2Response, authoritiesList)
}
else
defaultAuth
}
}
)
)
I don't know if there is a simpler way to do it but here we basically use the default behaviour but modify it the way we want to. Formally, the list of assertions may contain more than one assertion so we deal with that by using multiple lists which are then flattened and turned into a set (you could just use the first assertion only if you prefer, since there are usually just one).
Spring Security >= 5.4.0
What changed this time was that OpenSamlAuthenticationProvider
got deprecated due to the fact that it uses OpenSAML3 which has reached the end of its lifetime and has been replaced by OpenSAML4. We now have to use OpenSaml4AuthenticationProvider
. Apart from this, the configuration part stays the same
.saml2Login()
.authenticationManager(
ProviderManager(
OpenSaml4AuthenticationProvider().apply {
setResponseAuthenticationConverter {
val defaultAuth = OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter().convert(it);
if (defaultAuth !== null && defaultAuth.isAuthenticated) {
val authoritiesLists = it.response.assertions.map { ass ->
// return a list of authorities based on the assertion
}
val authoritiesList = authoritiesLists.flatten().toSet()
Saml2Authentication(defaultAuth.principal as AuthenticatedPrincipal, defaultAuth.saml2Response, authoritiesList)
}
else
defaultAuth
}
}
)
)
Nonetheless, this will not work out of the box, and you have to do some modifications to your package statements in your project to force Spring Security to use OpenSAML4 instead of OpenSAML3 (which is the default due to backward compatibility).
Below is what it would look like using Gradle
dependencies {
constraints {
implementation "org.opensaml:opensaml-core:4.1.1"
implementation "org.opensaml:opensaml-saml-api:4.1.1"
implementation "org.opensaml:opensaml-saml-impl:4.1.1"
}
...
}
Now you're set! Remember that the first argument to the Saml2Authentication
is the user principal (inherits from AuthenticatedPrincipal
) which could also be customized according to your own desire and needs. Just implement a user class that inherits from AuthenticatedPrincipal
and add logic to the response authentication converter and you're good to go.
Links