Spring Boot SAML2 - How to skip the select an identity provider page

Published
Updated

With Spring Security 5.2’s new SAML2 library, you may want users to be redirected past the identity provider selection page - specifically if there is only one identity provider configured in your application.

If you want to skip the IDP (identity provider) selection page in Spring Boot, you will need to add a filter in the chain before the SelectIdentityProviderFilter. See below for the configuration:

package com.codetinkering.example.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.saml.provider.provisioning.SamlProviderProvisioning;
import org.springframework.security.saml.provider.service.SelectIdentityProviderFilter;
import org.springframework.security.saml.provider.service.ServiceProviderService;
import org.springframework.security.saml.provider.service.config.SamlServiceProviderSecurityConfiguration;
import org.springframework.security.saml.provider.service.config.SamlServiceProviderSecurityDsl;

import com.codetinkering.example.filter.SingleProviderRedirectFilter;


@EnableWebSecurity
public class SecurityConfig {

    @Configuration
    @Order(1)
    public static class SamlSecurity extends SamlServiceProviderSecurityConfiguration {

        private AppConfig appConfig;

        public SamlSecurity(BeanConfig beanConfig, @Qualifier("appConfig") AppConfig appConfig) {
            super("/saml/sp/", beanConfig);
            this.appConfig = appConfig;
        }

        @Autowired
        private SamlProviderProvisioning<ServiceProviderService> samlServiceProviderProvisioning;
        
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            http.apply(SamlServiceProviderSecurityDsl.serviceProvider())
                .configure(appConfig)

                .and()
            .addFilterBefore(new SingleProviderRedirectFilter(samlServiceProviderProvisioning), SelectIdentityProviderFilter.class);
        }
    }

    @Configuration
    public static class AppSecurity extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .antMatcher("/**")
                .authorizeRequests()
                .antMatchers("/**").authenticated()
                .and()
                .formLogin().loginPage("/saml/sp/select")
            ;
        }
    }

}

Next, you will need to create a filter which will perform the redirect for the user to the identity provider page:

package com.codetinkering.example.filter;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.saml.provider.SamlFilter;
import org.springframework.security.saml.provider.config.ExternalProviderConfiguration;
import org.springframework.security.saml.provider.provisioning.SamlProviderProvisioning;
import org.springframework.security.saml.provider.service.ModelProvider;
import org.springframework.security.saml.provider.service.ServiceProviderService;
import org.springframework.security.saml.provider.service.config.LocalServiceProviderConfiguration;
import org.springframework.security.saml.saml2.metadata.IdentityProviderMetadata;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;

public class SingleProviderRedirectFilter extends SamlFilter<ServiceProviderService> {


    public SingleProviderRedirectFilter(SamlProviderProvisioning<ServiceProviderService> provisioning) {
        super(provisioning);
    }
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

            ServiceProviderService provider = getProvisioning().getHostedProvider();
            LocalServiceProviderConfiguration configuration = provider.getConfiguration();
            List<ModelProvider> providers = new LinkedList<>();
            configuration.getProviders().stream().forEach(
                p -> {
                    try {
                        
                        final String redirect = getDiscoveryRedirect(provider, request, p);
                        System.out.println("sending redirect -> " + redirect);
                        response.sendRedirect(redirect);
                        return;
                    } catch (Exception x) {
                        x.printStackTrace();
                    }
                }
            );
    }

    
    protected String getDiscoveryRedirect(ServiceProviderService provider,
              HttpServletRequest request,
              ExternalProviderConfiguration p) throws UnsupportedEncodingException {
        
        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(
        provider.getConfiguration().getBasePath()
        );
        builder.pathSegment("saml/sp/discovery");
        IdentityProviderMetadata metadata = provider.getRemoteProvider(p);
        builder.queryParam("idp", UriUtils.encode(metadata.getEntityId(), UTF_8.toString()));
        return builder.build().toUriString();
    }
    
}