We have an application which uses spring security saml for SSO authentication for a client. We needed to add a new SSO authentication for our internal users on the login page, so user_client need to click on sso_login_client button to be redirected on its IdP to log in, and user_internal on sso_login_internal button to be redirected on our IdP.
The problem is that our configuration is implemented for only one IdP. What do we need to change to support another IdP ?
Here is our configuration :
SecurityConfig.java
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig {
@Configuration
@Order(1)
@Profile("SSO")
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter implements InitializingBean, DisposableBean {
private static final Logger logger = Logger.getLogger(SecurityConfig.class);
private Timer backgroundTaskTimer;
private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;
private String metadataUrl = null;
private String entityID = null;
private String entityBaseURL = null;
private String metadataFile = null;
private String metadataRetrievalMode = null;
private Long maxAuthenticationAge = null;
...
// SAML Authentication Provider responsible for validating of received SAML
// messages
@Bean
public CustomSamlAuthenticationProvider samlAuthenticationProvider() {
CustomSamlAuthenticationProvider customSamlAuthenticationProvider = new CustomSamlAuthenticationProvider();
customSamlAuthenticationProvider.setForcePrincipalAsString(false);
return customSamlAuthenticationProvider;
}
...
// Setup advanced info about metadata
@Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
extendedMetadata.setSignMetadata(false);
extendedMetadata.setEcpEnabled(true);
return extendedMetadata;
}
@Bean
@Qualifier("idp-adfs")
public ExtendedMetadataDelegate adfsExtendedMetadataProvider()
throws MetadataProviderException {
ExtendedMetadataDelegate extendedMetadataDelegate = null;
if ("FILE".equals(metadataRetrievalMode)) {
FilesystemMetadataProvider fileSystemMetadataProvider = new FilesystemMetadataProvider(new File(metadataFile));
fileSystemMetadataProvider.setParserPool(parserPool());
extendedMetadataDelegate = new ExtendedMetadataDelegate(fileSystemMetadataProvider, extendedMetadata());
extendedMetadataDelegate.setMetadataTrustCheck(false);
extendedMetadataDelegate.setMetadataRequireSignature(false);
} else {
String idpKeycloakMetadataURL = metadataUrl;
HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(
this.backgroundTaskTimer, httpClient(), idpKeycloakMetadataURL);
httpMetadataProvider.setParserPool(parserPool());
extendedMetadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider, extendedMetadata());
// Disable metadata trust check to prevent "Signature trust establishment failed for metadata entry" exception
extendedMetadataDelegate.setMetadataTrustCheck(false);
extendedMetadataDelegate.setMetadataRequireSignature(false);
}
backgroundTaskTimer.purge();
return extendedMetadataDelegate;
}
// IDP Metadata configuration - paths to metadata of IDPs in circle of trust
// is here
// Do no forget to call iniitalize method on providers
@Bean
@Qualifier("metadata")
public CachingMetadataManager metadata() throws MetadataProviderException {
List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
providers.add(adfsExtendedMetadataProvider());
CachingMetadataManager metadataManager=new CachingMetadataManager(providers);
metadataManager.setDefaultIDP(metadataUrl);
return new CachingMetadataManager(providers);
}
// Filter automatically generates default SP metadata
@Bean
public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId(entityID);
if (entityBaseURL != null) {
metadataGenerator.setEntityBaseURL(entityBaseURL);
}
metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager());
metadataGenerator.setRequestSigned(false);
return metadataGenerator;
}
...
@Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOHoKProcessingFilter;
}
// Processing filter for WebSSO profile messages
@Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new CustomSamlProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOProcessingFilter;
}
@Bean
public MetadataGeneratorFilter metadataGeneratorFilter() {
return new MetadataGeneratorFilter(metadataGenerator());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http
.httpBasic()
.authenticationEntryPoint(samlEntryPoint());
http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(),BasicAuthenticationFilter.class)
.addFilterBefore(samlFilter(), CsrfFilter.class);
http .requestMatchers()
.antMatchers("/loginSSO/**","/saml/**").and().authorizeRequests()
.anyRequest().authenticated();
http
.logout()
.disable(); // The logout procedure is already handled by SAML filters.
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(samlAuthenticationProvider());
}
}
}
LoginSSOController.java
@Controller
public class LoginSSOController {
// Logger
private static final Logger logger = Logger.getLogger(LoginSSOController.class);
@RequestMapping(value = "/loginSSO", method = RequestMethod.GET)
public View idpSelection(HttpServletRequest request, Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null) {
logger.debug("Current authentication instance from security context is null");
}else {
logger.debug("Current authentication instance from security context: ");
}
if (auth == null) {
RedirectView redirect = new RedirectView("/login");
redirect.setExposeModelAttributes(false);
return redirect;
}
RedirectView redirect = new RedirectView("/dashboard");
redirect.setExposeModelAttributes(false);
return redirect;
}
}
login.html
<a href="/loginSSO" class="logAD btn"><i class="fa fa-windows">
</i> <span th:text="${loginPageSSOLabel}">Login with SSO</span>
</a>
I heard about RelyingPartyRegistration but i don't know if it's the right solution for our configuration and how to implement it.
Also i am wondering how does spring-security distinguish multiple IdPs for different users ? Do we need to create a new endpoint like "/loginSSO" ?
Thank you for your help in advance.