Search code examples
javaspringspring-securitysaml-2.0spring-security-saml2

Spring Security SAML2 dynamic selection of IDPs or dynamic URLs for them


We're trying to configure several identity providers in the application to support different types of SSO. The problem is that for a non-authenticated request the application doesn't know which IDP to redirect to. We can figure out which IDP to use based on the domain name. That's not a problem. The problem is to change filters the way to redirect to that specific IDP instead of the first found one.

I wonder if there's an easy way to support it in Spring Security or its SAML2 library.

I can either somehow modify metadata to redirect it to my own URL (and then have some custom code there) or make the authentication filter pick the right IDP based on some criteria.

UPDATE

Current yaml configuration:

spring:
  security:
    saml2:
      relyingparty:
        registration:
          idpone:
            identityprovider:
              entity-id: https://idpone.com
              sso-url: https://idpone.com
              verification: 
                credentials:
                - certificate-location: "classpath:saml/idpone.crt"
          idptwo:
            identityprovider:
              entity-id: https://idptwo.com
              sso-url: https://idptwo.com
              verification: 
                credentials:
                - certificate-location: "classpath:saml/idptwo.crt"

Solution

  • As of Spring Security 5.2, you can set up multiple IDPs via the RelyingPartyRegistrationRepository.

    Each one looks something like this when using Spring Boot:

    spring:
      security:
        saml2:
          relyingparty:
            registration:
              idpone:
                identityprovider:
                  verification:
                    credentials:
                      - certificate-location: "classpath:idpOne.crt"
                  entity-id: https://idp.example.org
                  sso-url: https://idp.example.org/SSOService.saml2
              idptwo:
                identityprovider:
                  ...
    

    Then, you can initiate an AuthNRequest for idpOne by naving to http://localhost:8080/saml2/authenticate/idpOne.

    By Hostname

    If you want to do it by hostname, then you can customize the /login page to know which of the /saml2/authenticate/{registrationId} endpoints to redirect to.

    First, you'd tell Spring Security that you have a custom /login page, so it doesn't create one:

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) {
            http
                .authorizeRequests(authz -> authz
                    .mvcMatchers("/login").permitAll() // here
                    .anyRequest().authenticated())
                .saml2Login(saml2 -> saml2.loginPage("/login")) // and here
        }
    
    }
    

    And then, you'd define it:

    @Controller
    public class LoginController {
        private final RelyingPartyRegistrationRepository relyingParties;
    
        // ... constructor
    
        @GetMapping("/login")
        public void login(HttpServletRequest request, HttpServletResponse response) {
            String registrationId = // ... derive from the host name
            RelyingPartyRegistration relyingParty = this.relyingParties
                    .findByRegistrationId(registrationId);
            if (relyingParty == null) {
                response.setStatusCode(401);
            } else {
                response.sendRedirect("/saml2/authenticate/" + registrationId);
            }
        }
    }
    

    The reason for the lookup in the /login endpoint is to ensure that the registrationId supplied in the hostname is legitimate.