18

I am trying to integrate SAML SSO with Spring Security using Spring Security SAML extension. Before, I succeeded to run a proof of concept found here: https://github.com/vdenotaris/spring-boot-security-saml-sample. Unfortunately, after moving the configuration to my project it is not working correctly.

After analyzing the logs, I figured out that my application (SP) is correctly downloading the IdP metadata from provided URL. However, after trying to download metadata of my SP by trying https://localhost:8443/saml/metadata in browser, the following exception is thrown:

javax.servlet.ServletException: Error initializing metadata
at org.springframework.security.saml.metadata.MetadataDisplayFilter.processMetadataDisplay(MetadataDisplayFilter.java:120)
at org.springframework.security.saml.metadata.MetadataDisplayFilter.doFilter(MetadataDisplayFilter.java:88)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1645)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:564)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:578)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1111)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:498)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:183)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1045)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:98)
at org.eclipse.jetty.server.Server.handle(Server.java:461)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:284)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:244)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:534)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:607)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:536)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.opensaml.saml2.metadata.provider.MetadataProviderException: No hosted service provider is configured and no alias was selected
    at org.springframework.security.saml.context.SAMLContextProviderImpl.populateLocalEntity(SAMLContextProviderImpl.java:311)
    at org.springframework.security.saml.context.SAMLContextProviderImpl.populateLocalContext(SAMLContextProviderImpl.java:216)
    at org.springframework.security.saml.context.SAMLContextProviderImpl.getLocalEntity(SAMLContextProviderImpl.java:107)
    at org.springframework.security.saml.metadata.MetadataDisplayFilter.processMetadataDisplay(MetadataDisplayFilter.java:114)
    ... 24 more

After debugging, I was not able to figure out why Spring is not able to figure out entity ID of my application. I am setting it like this:

// Filter automatically generates default SP metadata
@Bean
public MetadataGenerator metadataGenerator() {
    MetadataGenerator metadataGenerator = new MetadataGenerator();
    metadataGenerator.setEntityId(environment.getRequiredProperty("saml.entity-id"));
    metadataGenerator.setEntityBaseURL("URL is here");
    metadataGenerator.setExtendedMetadata(extendedMetadata());
    metadataGenerator.setIncludeDiscoveryExtension(false);
    metadataGenerator.setKeyManager(keyManager());
    return metadataGenerator;
}

Of course the saml.entity-id property is correctly downloaded from my configuration. Whole security config is here: https://gist.github.com/mc-suchecki/671ecb4d5ae4bae17f81

Order of the filters is correct - the Metadata Generator Filter is before the SAML Filter. I am not sure that is relevant - I suppose not - but my application is not using Spring Boot - and the sample application (the source of the configuration) is.

Thank you in advance for any help.

mc.suchecki
  • 1,898
  • 4
  • 23
  • 44
  • Have you tried using url : https://localhost:8443//saml/metadata in the browser to download metadata? The url you are trying is not having application name at all. – BK Elizabeth Jan 20 '16 at 10:46
  • I think the URL is correct - if not, there will be 404 status code instead of 500 status code and no exception. – mc.suchecki Jan 20 '16 at 10:48
  • I was also getting this error when using spring-saml-extension for SSO. Can you please tell me the value of saml.entity-id property – BK Elizabeth Jan 20 '16 at 11:19
  • To be precise following is the code snippet of method from SAMLContextProviderImpl class where you are getting error. It seems your entity id is null. void populateLocalEntity(SAMLMessageContext samlContext) throws MetadataProviderException { String localEntityId = samlContext.getLocalEntityId(); QName localEntityRole = samlContext.getLocalEntityRole(); if (localEntityId == null) { throw new MetadataProviderException("No hosted service provider is configured and no alias was selected"); } – BK Elizabeth Jan 20 '16 at 11:24
  • Yeah, exactly - the local entity ID is null in my SAMLContext. But the question is - why? – mc.suchecki Jan 20 '16 at 11:52
  • Can you please provide some more information like what is the application server,your application context root (I still have doubt that how you can access web application without any context root), framework used by your project. – BK Elizabeth Jan 21 '16 at 03:32
  • Also to add from the documentation of 'SAMLContextProviderImpl' class, 'MetadataProviderException' exception occurs if metadata do not contain expected entities or localAlias is specified but not found. Please try hard coding the entity id instead of loading from environment properties. The enity Id should be your application URL. (e.g., 'https://localhost:8443/MyApplication') – BK Elizabeth Jan 21 '16 at 04:32

5 Answers5

8

I found the issue this week. There was a problem with filters. One of the methods was creating the 'samlFilter', like this:

public FilterChainProxy samlFilter() throws Exception {
    List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), samlEntryPoint()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), samlLogoutFilter()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
        metadataDisplayFilter()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
        samlWebSSOProcessingFilter()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
        samlWebSSOHoKProcessingFilter()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
        samlLogoutProcessingFilter()));
    chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"), samlIDPDiscovery()));
    return new FilterChainProxy(chains);
}

After that, another method was setting the whole filter chain for Spring, like so:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.httpBasic().authenticationEntryPoint(samlEntryPoint());
    http.csrf().disable();
    http.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
        .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
    http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/error").permitAll()
        .antMatchers("/saml/**").permitAll().anyRequest().authenticated();
    http.logout().logoutSuccessUrl("/");
}

That was perfectly correct. However, when I was launching the application using Jetty server, I was trying to connect only the 'samlFilter' to the application context. Because of that, the 'metadataGeneratorFilter' that is required to be before the 'metadataDisplayFilter' was not added to the filter chain at all. When I changed 'samlFilter' to 'springSecurityFilter', everything started working. That was not easy to find, because of my non-standard use of Jetty.

Thank you for your help!

mc.suchecki
  • 1,898
  • 4
  • 23
  • 44
  • 1
    " When I changed 'samlFilter' to 'springSecurityFilter', everything started working. " can you please give a code sample for that? Do you mean that you added a Spring IOC Ref in securityContext.xml? I can not find any "springSecurityFilter" other than the Servlet defined in web.xml springSecurityFilterChain – tom Sep 12 '17 at 07:23
  • I am stuck on the similar issue. Could you share a code sample or elaborate "changing samlFilter to springSecurityFilter" ? – tryingToLearn Jan 16 '18 at 06:30
  • For me, it was basically the same story: the SAML configuration was loaded after the other Http Configuration. I have solved that by implementing the `@Order` annotation (load SAML configuration earlier than the other configuration). – Valentin Grégoire Sep 19 '19 at 10:30
2

In MetadataGenerator entityId is a shared key you use to communicate to your IDP that your application wants to access it. On IDP side there is a samlConfiguration where you need to enter the same entityId to enable your application to access to the IDP users.

<bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
<constructor-arg>
    <bean class="org.springframework.security.saml.metadata.MetadataGenerator">              
        <property name="entityId" value="****"/>
        <property name="extendedMetadata">
            <bean class="org.springframework.security.saml.web.MyExtendedMetadata">
                <property name="signMetadata" value="true"/>                        
                <property name="signingKey" value="****"/>
                <property name="encryptionKey" value="****"/>
            </bean>
        </property>
    </bean>
</constructor-arg>

Antonio
  • 644
  • 5
  • 17
  • That is unfortunately not the issue. My IDP is configured via web interface and it is working well when I am using it from another application. The problem is on SP side, and I am 100% sure about that. My SP is also correctly downloading IDP metadata, basing on the logs. – mc.suchecki Jan 24 '16 at 17:28
1

Do you have an IDP configured?

Ruddy
  • 89
  • 4
  • I think you do not have any entry with the saml.entity-id in your IDP – Ruddy Jan 22 '16 at 12:06
  • My IDP is configured via web interface and it is working well when I am using it from another application. The problem is on SP side, and I am 100% sure about that. My SP is also correctly downloading IDP metadata, basing on the logs. – mc.suchecki Jan 24 '16 at 17:49
1

I've found this exception (No hosted provider is configured). I was trying to work with a pre-configured metadata. If you want to work with a pre-configured metadata, you don't need metadataGeneratorFilter, so you can delete it from your configuration (commented on this sample code):

@Override
    protected void configure(HttpSecurity http) throws Exception {
        // TODO Auto-generated method stub
        http.authorizeRequests()
            .antMatchers("/logout.jsp").permitAll()
            .antMatchers("/loginFailed.jsp").permitAll()
            .antMatchers("/saml/**").permitAll()
        .anyRequest()
                .authenticated()
                .and().httpBasic().authenticationEntryPoint(samlEntryPoint())
                .and().requiresChannel().anyRequest().requiresInsecure();
        
        http
            //.addFilterBefore(metadataGeneratorFilter, ChannelProcessingFilter.class)
            .addFilterAfter(samlFilter, BasicAuthenticationFilter.class);
        
        http.csrf().disable();
                
    }

the next thing you have to do, is to add preconfigured metadata. You can do this as a bean on the root of your securityConfig.xml, or adding the bean on your cachingMetadataManager (both with xml configuration). And here were the problem. If you choose preconfigured metadata, you have to add extendedMetadata for this bean, specifying it as local:

    <bean id="metadata" class="org.springframework.security.saml.metadata.CachingMetadataManager">
    <constructor-arg>
        <list>
            <!-- PreConfigured SP MetaData -->
            <bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
                <constructor-arg>
                    <bean class="org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider">
                        <constructor-arg>
                            <bean class="java.util.Timer"/>
                        </constructor-arg>
                        <constructor-arg>
                            <bean class="org.opensaml.util.resource.ClasspathResource">
                                <constructor-arg value="/metadata/spMetadata.xml"/>
                            </bean>
                        </constructor-arg>
                        <property name="parserPool" ref="parserPool"/>
                    </bean>
                </constructor-arg>
                <constructor-arg>
                    <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
                        <property name="local" value="true"/>
                        <property name="alias" value="metadataAlias"/>
                    </bean>
                </constructor-arg>
            </bean>
            <!-- IP MetaData -->
mluelmo
  • 39
  • 3
0

I was able to resolve this by excluding the SAML filters from Spring Boot's auto-registration of regular (non-security) filters by adding a FilterRegistrationBean for each filter, such as

  @Bean
  public FilterRegistrationBean disableSAMLEntryPoint() {
    final FilterRegistrationBean registration = 
        new FilterRegistrationBean<>(samlEntryPoint());
    registration.setEnabled(false);
    return registration;
  }
itzg
  • 1,081
  • 2
  • 13
  • 11