Spring Security SAML插件 - 未配置托管服务提供者异常

18
我试图使用Spring Security SAML扩展程序将SAML SSO与Spring Security集成。之前,我成功地运行了这里找到的概念验证:https://github.com/vdenotaris/spring-boot-security-saml-sample。不幸的是,在将配置移动到我的项目后,它没有正确工作。经过分析日志,我发现我的应用程序(SP)正在从提供的URL下载IdP元数据。然而,在尝试通过浏览器下载我的SP的元数据https://localhost:8443/saml/metadata时,抛出了以下异常:
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

在调试后,我仍然无法弄清楚为什么Spring无法确定我的应用程序实体的ID。我像这样进行设置:

// 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;
}
当然,saml.entity-id属性从我的配置正确下载。整个安全配置在这里:https://gist.github.com/mc-suchecki/671ecb4d5ae4bae17f81
过滤器的顺序是正确的 - Metadata Generator Filter在SAML Filter之前。我不确定这是否相关 - 我认为不是 - 但我的应用程序没有使用Spring Boot,样例应用程序(配置源)有。
提前感谢任何帮助。

你尝试过在浏览器中使用以下URL:https://localhost:8443/<应用程序上下文根>/saml/metadata来下载元数据吗?你正在尝试的URL根本没有应用程序名称。 - BK Elizabeth
我认为URL是正确的 - 如果不正确,将会返回404状态码而不是500状态码,并且没有异常。 - mc.suchecki
当我使用Spring SAML扩展进行SSO时,我也遇到了这个错误。请问您能告诉我saml.entity-id属性的值吗? - BK Elizabeth
请提供更多信息,例如应用服务器是什么,您的应用程序上下文根是什么(我仍然怀疑您如何在没有任何上下文根的情况下访问Web应用程序),以及您的项目使用的框架。 - BK Elizabeth
此外,根据“SAMLContextProviderImpl”类的文档,如果元数据不包含预期实体或指定了本地别名但未找到,则会发生“MetadataProviderException”异常。请尝试硬编码实体ID,而不是从环境属性加载。实体ID应为您的应用程序URL。(例如,“https://localhost:8443/MyApplication”) - BK Elizabeth
显示剩余2条评论
5个回答

8

本周我发现了问题。过滤器出了问题。其中一个方法正在创建 'samlFilter',像这样:

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);
}

接下来,另一种方法是为Spring设置整个过滤器链,如下所示:

@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("/");
}

那是完全正确的。但是,当我使用Jetty服务器启动应用程序时,我尝试仅将'samlFilter'连接到应用程序上下文。因此,必须在'metadataDisplayFilter'之前添加的必需的'metadataGeneratorFilter'未被添加到过滤器链中。当我将'samlFilter'改为'springSecurityFilter'时,一切开始正常工作。由于我对Jetty的非标准使用,这不容易找到。
感谢您的帮助!

1
当我将'samlFilter'更改为'springSecurityFilter'时,一切开始正常工作。您能否提供一个代码示例?您是指在securityContext.xml中添加了Spring IOC Ref吗?除了在web.xml中定义的Servlet之外,我找不到任何“springSecurityFilter”。<filter-name>springSecurityFilterChain</filter-name> - tom
我遇到了类似的问题。你能分享一下代码示例或者详细说明一下“将samlFilter更改为springSecurityFilter”吗? - tryingToLearn
对我来说,这基本上是同样的故事:SAML配置在其他Http配置之后加载。我通过实现@Order注释(比其他配置更早地加载SAML配置)来解决了这个问题。 - Valentin Grégoire

2
在MetadataGenerator中,entityId是您用于向IDP传达您的应用程序想要访问它的共享密钥。在IDP端,有一个samlConfiguration,在其中需要输入相同的entityId,以使您的应用程序能够访问IDP用户。
<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>


很遗憾,这不是问题所在。我的IDP是通过Web界面配置的,在我从另一个应用程序中使用它时运行良好。问题出在SP方面,我对此非常确定。根据日志,我的SP也正确下载了IDP元数据。 - mc.suchecki

1

你是否配置了IDP?


我认为您的IDP中没有任何带有saml.entity-id的条目。 - Ruddy
我的IDP是通过Web界面配置的,当我从另一个应用程序中使用它时,它运行良好。问题在SP方面,我对此非常确定。根据日志,我的SP也正确下载了IDP元数据。 - mc.suchecki

1
我发现了这个异常(未配置托管提供程序)。我试图使用预配置的元数据进行工作。 如果您想要使用预配置的元数据,您不需要metadataGeneratorFilter,所以您可以从配置中删除它(在此示例代码中已注释)。
@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();
                
    }

接下来你需要做的事是添加预配置元数据。你可以在securityConfig.xml的根上作为bean进行操作,也可以将该bean添加到cachingMetadataManager上(均使用xml配置)。这里有一个问题,如果你选择预配置元数据,则必须在此bean上添加extendedMetadata,并将其指定为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 -->

0

我通过为每个过滤器添加FilterRegistrationBean,从而将SAML过滤器从Spring Boot的自动注册常规(非安全)过滤器中排除,成功解决了这个问题。

  @Bean
  public FilterRegistrationBean disableSAMLEntryPoint() {
    final FilterRegistrationBean registration = 
        new FilterRegistrationBean<>(samlEntryPoint());
    registration.setEnabled(false);
    return registration;
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接