Spring Security SAML Assertion to Roles conversion
Asked Answered
B

1

7

I have been using SAML 2.0 with Spring Boot 2.5.6 using Okta as the Identity Provider. For the most part, I have been able to create a Web Application and integrate with Okta's Identity provider. The problem I am facing is related to roles. The groups that I assign in Okta do not translate to roles in Spring Security and it's not clear how this conversion is supposed to happen in Spring Security for SAML. In the SAML assertion, the group attribute is named as "groups" and I can see that in the Assertion XML that comes in as part of the Response. But Spring Security does not understand "groups" attribute as roles.

After Authentication, the SAML2Authentication object contains Authorities as ROLE_USER. How do I translate the incoming group attributes in Assertion to the proper authorities in SAML2Authentication ? Without that I cannot configure the Application URLs to the proper roles for Authorization.

Please help or point me in some direction

Basrhin answered 13/11, 2021 at 17:1 Comment(0)
G
7

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

Geophilous answered 14/11, 2021 at 11:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.