Spring Boot 3.0 - SAML Login redirect not working

1.8k Views Asked by At

I'm currently upgrading from Spring Boot 2.7.7 to Spring Boot 3.0.1.

Unfortunately, the SAML-Redirect that works under Spring Boot 2.x does not work anymore.

I had to replace some deprecated code with what is recommended by Spring according to this commit: https://github.com/spring-projects/spring-security/commit/953c9294d0912d65cd90c8e729a47a0d74697392

under Spring Boot 2.x

@Bean
public Saml2AuthenticationRequestContextResolver saml2AuthenticationRequestContextResolver(
       RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
    return new DefaultSaml2AuthenticationRequestContextResolver(relyingPartyRegistrationResolver);
}

under Spring Boot 3.x

@Bean
public Saml2AuthenticationRequestResolver saml2AuthenticationRequestResolver(
        RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
    return new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationResolver);
}

This was the only change I made in the class Saml2WebSecurityConfig, that is now as follows:

@EnableWebSecurity
@Configuration
public class Saml2WebSecurityConfig {

    private final ConfigProperties configProperties;

    public Saml2WebSecurityConfig(ConfigProperties configProperties) {
        this.configProperties = configProperties;
    }

    @Bean
    public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() throws Exception {
        // Generate signing with private key and public key
        Saml2X509Credential signingCredential = null; // not relevant for context
        // Single verification certificate
        Saml2X509Credential verificationCredential =  null; // not relevant for context

        RelyingPartyRegistration registration = RelyingPartyRegistration
                .withRegistrationId(configProperties.getRegistrationId())
                .entityId(configProperties.getEntity())
                .signingX509Credentials(c -> c.add(signingCredential))
                .singleLogoutServiceLocation(configProperties.getSingleLogoutServiceLocation())
                .assertingPartyDetails(party -> party.entityId(configProperties.getAssertionId())
                        .singleSignOnServiceLocation(configProperties.getSingleSignOnServiceLocation())
                        .singleSignOnServiceBinding(Saml2MessageBinding.POST)
                        .wantAuthnRequestsSigned(true)
                        .signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256))
                        .verificationX509Credentials(c -> c.add(verificationCredential))
                )
                .build();
        return new InMemoryRelyingPartyRegistrationRepository(registration);
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authorize -> authorize
                        .anyRequest().authenticated())
                .saml2Login(saml2 -> {
                    try {
                        saml2.relyingPartyRegistrationRepository(this.relyingPartyRegistrationRepository());
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }).saml2Logout(withDefaults());
        http.csrf().disable();
        return http.build();
    }

    @Bean
    public Saml2AuthenticationRequestResolver saml2AuthenticationRequestResolver(
            RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
        return new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationResolver);
    }

    @Bean
    public RelyingPartyRegistrationResolver relyingPartyRegistrationResolver(
            RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
        return new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository);
    }
}

This are the dependencies / repositories (effective-pom):

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.0.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>6.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>6.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>3.0.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>6.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>6.0.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-saml2-service-provider</artifactId>
            <version>6.0.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>6.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.6</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>6.0.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>6.0.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <version>3.0.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.opensaml</groupId>
            <artifactId>opensaml-xmlsec-api</artifactId>
            <version>4.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.0.2</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>10.1.4</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>jackson-databind-nullable</artifactId>
            <version>0.2.4</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.14.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>io.swagger.core.v3</groupId>
            <artifactId>swagger-annotations-jakarta</artifactId>
            <version>2.2.8</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
            <version>3.0.2</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.opensaml</groupId>
            <artifactId>opensaml-saml-api</artifactId>
            <version>4.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.opensaml</groupId>
            <artifactId>opensaml-core</artifactId>
            <version>4.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.opensaml</groupId>
            <artifactId>opensaml-saml-impl</artifactId>
            <version>4.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>net.shibboleth.utilities</groupId>
            <artifactId>java-support</artifactId>
            <version>8.4.0</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <id>central</id>
            <url>https://repo1.maven.org/maven2</url>
        </repository>
        <repository>
            <id>Shibboleth</id>
            <name>Shibboleth</name>
            <url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
        </repository>
    </repositories>

Edit: How the user is extracted:

@Profile("!local")
@RequestScope
@Component
public class SamlUserContext implements UserContext {

    @Override
    public LoggedInUser user() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (!(authentication instanceof Saml2Authentication)) {
            String message = String.format("The type %s is not the expected Saml2Authentication", authentication.getClass());
            throw new IllegalStateException(message);
        }

        if (!(authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal saml2AuthenticatedPrincipal)) {
            String message = String.format("The type %s is not the expected Saml2AuthenticatedPrincipal",
                    authentication.getPrincipal().getClass());
            throw new IllegalStateException(message);
        }

        return new SamlUser(saml2AuthenticatedPrincipal);
    }
}
0

There are 0 best solutions below