如何使用 Spring Security SAML 配置远程发现?

Posted

技术标签:

【中文标题】如何使用 Spring Security SAML 配置远程发现?【英文标题】:How to configure the remote discovery with Spring Security SAML? 【发布时间】:2016-02-17 21:44:47 【问题描述】:

我正在尝试配置 Spring Security SAML 1.0.1 以访问位于 https://discovery.renater.fr/test 的远程发现服务。而是到达“CachingMetadataManager”的属性defaultIDP 中指定的IDP。

在Spring Security SAML 1.0.1 documentation,我们可以阅读:

远程发现服务

为了启用外部 IDP 发现服务,请将本地 SP 扩展元数据中的属性 idpDiscoveryURL 配置为外部发现 URL。确保属性 idpDiscoveryEnabled 设置为 true。远程发现服务需要支持身份提供者发现服务协议和配置文件。

这是我生成 SP 元数据的方式:

<!-- Filter automatically generates default SP metadata -->
<b:bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
    <b:constructor-arg>
        <b:bean class="org.springframework.security.saml.metadata.MetadataGenerator">
            <b:property name="includeDiscoveryExtension" value="true"/>
            <b:property name="extendedMetadata">
                <b:bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
                    <b:property name="idpDiscoveryEnabled" value="true"/>
                    <b:property name="idpDiscoveryURL" value="https://discovery.renater.fr/test"/>
                    <b:property name="idpDiscoveryResponseURL" value="http://acem.u-bretagneloire.fr/ACEM/saml/login/alias/defaultAlias?disco=true"/>
                </b:bean>
            </b:property>
        </b:bean>
    </b:constructor-arg>
</b:bean>

可以看出,我在 bean MetadataGenerator 中将属性 includeDiscoveryExtension 设置为 true。我还在 bean ExtendedMetadata 中设置了属性 idpDiscoveryEnabledidpDiscoveryURLidpDiscoveryResponseURL。但是当我将应用程序的日志级别设置为“TRACE”时,idpDiscoveryURL 值永远不会显示。

问题:我的配置中缺少什么来访问发现 URL?


完整的 Spring SAML 配置文件是:

<?xml version="1.0" encoding="UTF-8" ?>
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
   xmlns="http://www.springframework.org/schema/security"
   xmlns:p="http://www.springframework.org/schema/p"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
          http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!-- Enable auto-wiring -->
<context:annotation-config/>

<!-- Scan for auto-wiring classes in spring saml packages -->
<context:component-scan base-package="org.springframework.security.saml"/>

<!-- 
<http security="none" pattern="/favicon.ico"/>
<http security="none" pattern="/images/**"/>
<http security="none" pattern="/css/**"/>
<http security="none" pattern="/logout.jsp"/>
-->
<!-- Security for the administration UI -->
<http pattern="/saml/web/**" use-expressions="false">
    <access-denied-handler error-page="/saml/web/metadata/login"/>
    <form-login login-processing-url="/saml/web/login" login-page="/saml/web/metadata/login" default-target-url="/saml/web/metadata"/>
    <intercept-url pattern="/saml/web/metadata/login" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
    <intercept-url pattern="/saml/web/**" access="ROLE_ADMIN"/>
    <custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
</http>

<!-- Secured pages with SAML as entry point -->
<!-- 
<http entry-point-ref="samlEntryPoint" use-expressions="false">
    <intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
    <custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
    <custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
</http>
-->
<http entry-point-ref="samlEntryPoint"  use-expressions="true" auto-config="true">
    <!-- For Spring Security 4.x, we need to disable csrf, otherwise AJAX requests get 403:-->
    <csrf disabled="true"/>
    <intercept-url access="permitAll" pattern="/" /><!-- To permit "/" allows the use of web.xml's <welcome-file> -->
    <intercept-url access="permitAll" pattern="/home" />
    <intercept-url access="permitAll" pattern="/pages/exceptions/**" />
    <intercept-url access="permitAll" pattern="/javax.faces.resource/**" />
    <intercept-url access="permitAll" pattern="/resources/**" />
    <intercept-url access="hasRole('ROLE_ADMIN')" pattern="/administration/**" />
    <intercept-url access="hasRole('ROLE_ADMIN')" pattern="/rest/**" />
    <intercept-url access="isAuthenticated()" pattern="/**"/><!-- When the user is authentificated by the IDP, but doesn't exist in the application database -->
    <form-login login-page="/login-page-should-not-be-generated-when-using-saml" />
    <logout logout-url="/logout" logout-success-url="/home"/>
    <custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
    <custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
</http>

<!-- Filters for processing of SAML messages -->
<b:bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
    <filter-chain-map request-matcher="ant">
        <filter-chain pattern="/saml/login/**" filters="samlEntryPoint"/>
        <filter-chain pattern="/saml/logout/**" filters="samlLogoutFilter"/>
        <filter-chain pattern="/saml/metadata/**" filters="metadataDisplayFilter"/>
        <filter-chain pattern="/saml/SSO/**" filters="samlWebSSOProcessingFilter"/>
        <filter-chain pattern="/saml/SSOHoK/**" filters="samlWebSSOHoKProcessingFilter"/>
        <filter-chain pattern="/saml/SingleLogout/**" filters="samlLogoutProcessingFilter"/>
        <filter-chain pattern="/saml/discovery/**" filters="samlIDPDiscovery"/>
    </filter-chain-map>
</b:bean>

<!-- Handler deciding where to redirect user after successful login -->
<b:bean id="successRedirectHandler"
      class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
    <b:property name="defaultTargetUrl" value="/"/>
    <b:property name="alwaysUseDefaultTargetUrl" value="true"/>
</b:bean>
<!--
Use the following for interpreting RelayState coming from unsolicited response as redirect URL:
<b:bean id="successRedirectHandler" class="org.springframework.security.saml.SAMLRelayStateSuccessHandler">
   <b:property name="defaultTargetUrl" value="/" />
</b:bean>
-->

<!-- Handler deciding where to redirect user after failed login -->
<b:bean id="failureRedirectHandler"
      class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
    <b:property name="useForward" value="true"/>
    <b:property name="defaultFailureUrl" value="/error.jsp"/>
</b:bean>

<!-- Handler for successful logout -->
<b:bean id="successLogoutHandler" class="org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler">
    <b:property name="defaultTargetUrl" value="/logout.jsp"/>
</b:bean>

<authentication-manager alias="authenticationManager">
    <!-- Register authentication manager for SAML provider -->
    <authentication-provider ref="authProvider"/>
    <!-- Register authentication manager for administration UI -->
    <authentication-provider>
        <user-service id="adminInterfaceService">
            <user name="admin" password="admin" authorities="ROLE_ADMIN"/>
        </user-service>
    </authentication-provider>
</authentication-manager>

<!-- Logger for SAML messages and events -->
<b:bean id="samlLogger" class="org.springframework.security.saml.log.SAMLDefaultLogger"/>

<!-- Central storage of cryptographic keys -->
<b:bean id="keyManager" class="org.springframework.security.saml.key.JKSKeyManager">
    <b:constructor-arg value="classpath:security/samlKeystore.jks"/>
    <b:constructor-arg type="java.lang.String" value="nalle123"/>
    <b:constructor-arg>
        <b:map>
            <b:entry key="apollo" value="nalle123"/>
        </b:map>
    </b:constructor-arg>
    <b:constructor-arg type="java.lang.String" value="apollo"/>
</b:bean>

<!-- Entry point to initialize authentication, default values taken from properties file -->
<b:bean id="samlEntryPoint" class="org.springframework.security.saml.SAMLEntryPoint">
    <b:property name="defaultProfileOptions">
        <b:bean class="org.springframework.security.saml.websso.WebSSOProfileOptions">
            <b:property name="includeScoping" value="false"/>
        </b:bean>
    </b:property>
</b:bean>

<!-- IDP Discovery Service -->
<b:bean id="samlIDPDiscovery" class="org.springframework.security.saml.SAMLDiscovery">
    <b:property name="idpSelectionPath" value="/WEB-INF/security/idpSelection.jsp"/>
</b:bean>

<!-- Filter automatically generates default SP metadata -->
<b:bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
    <b:constructor-arg>
        <b:bean class="org.springframework.security.saml.metadata.MetadataGenerator">
            <b:property name="includeDiscoveryExtension" value="true"/>
            <b:property name="extendedMetadata">
                <b:bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
                    <b:property name="idpDiscoveryEnabled" value="true"/>
                    <b:property name="idpDiscoveryURL" value="https://discovery.renater.fr/test"/>
                    <b:property name="idpDiscoveryResponseURL" value="http://acem.u-bretagneloire.fr/ACEM/saml/login/alias/defaultAlias?disco=true"/>
                </b:bean>
            </b:property>
        </b:bean>
    </b:constructor-arg>
</b:bean>

<!-- The filter is waiting for connections on URL suffixed with filterSuffix and presents SP metadata there -->
<b:bean id="metadataDisplayFilter" class="org.springframework.security.saml.metadata.MetadataDisplayFilter"/>

<!-- Configure HTTP Client to accept certificates from the keystore for HTTPS verification -->
<!--
<b:bean class="org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer">
    <b:property name="sslHostnameVerification" value="default"/>
</b:bean>
-->

<!-- IDP Metadata configuration - paths to metadata of IDPs in circle of 
    trust is here -->
<b:bean id="metadata"
    class="org.springframework.security.saml.metadata.CachingMetadataManager">
    <b:constructor-arg>
        <b:list>
            <b:bean
                class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
                <b:constructor-arg>
                    <b:bean
                        class="org.opensaml.saml2.metadata.provider.HTTPMetadataProvider">
                        <!-- URL containing the metadata -->
                        <b:constructor-arg>
                            <b:value type="java.lang.String">https://federation.renater.fr/test/renater-test-metadata.xml</b:value>
                        </b:constructor-arg>
                        <!-- Timeout for metadata loading in ms -->
                        <b:constructor-arg>
                            <b:value type="int">15000</b:value>
                        </b:constructor-arg>
                        <b:property name="parserPool" ref="parserPool" />
                    </b:bean>
                </b:constructor-arg>
                <b:constructor-arg>
                    <!-- Default extended metadata for entities not specified in the map -->
                    <b:bean
                        class="org.springframework.security.saml.metadata.ExtendedMetadata">
                    </b:bean>
                </b:constructor-arg>
                <b:constructor-arg>
                    <!-- Extended metadata for specific IDPs -->
                    <b:map>
                        <b:entry key="http://idp.ssocircle.com">
                            <b:bean
                                class="org.springframework.security.saml.metadata.ExtendedMetadata" />
                        </b:entry>
                    </b:map>
                </b:constructor-arg>
            </b:bean>
        </b:list>
    </b:constructor-arg>
    <!-- OPTIONAL used when one of the metadata files contains information 
        about this service provider -->
    <!-- <b:property name="hostedSPName" value=""/> -->
    <!-- OPTIONAL property: can tell the system which IDP should be used for 
        authenticating user by default. -->
    <b:property name="defaultIDP" value="https://ident-shib-test.univ-rennes1.fr/idp/shibboleth"/>
</b:bean>

<!--
    NOTE: In a real application you should not use an in memory implementation. You will also want
          to ensure to clean up expired tickets by calling ProxyGrantingTicketStorage.cleanup()
 -->
<b:bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>

<!-- SAML Authentication Provider responsible for validating of received SAML messages -->
<b:bean id="authProvider" class="org.springframework.security.saml.SAMLAuthenticationProvider">
    <!-- OPTIONAL property: can be used to store/load user data after login -->
    <b:property name="userDetails">
        <b:bean class="eu.ueb.acem.services.auth.SamlAuthenticationUserDetailsService"/>
    </b:property>
</b:bean>

<!-- Provider of default SAML Context -->
<b:bean id="contextProvider" class="org.springframework.security.saml.context.SAMLContextProviderImpl"/>

<!-- Processing filter for WebSSO profile messages -->
<b:bean id="samlWebSSOProcessingFilter" class="org.springframework.security.saml.SAMLProcessingFilter">
    <b:property name="authenticationManager" ref="authenticationManager"/>
    <b:property name="authenticationSuccessHandler" ref="successRedirectHandler"/>
    <b:property name="authenticationFailureHandler" ref="failureRedirectHandler"/>
</b:bean>

<!-- Processing filter for WebSSO Holder-of-Key profile -->
<b:bean id="samlWebSSOHoKProcessingFilter" class="org.springframework.security.saml.SAMLWebSSOHoKProcessingFilter">
    <b:property name="authenticationManager" ref="authenticationManager"/>
    <b:property name="authenticationSuccessHandler" ref="successRedirectHandler"/>
    <b:property name="authenticationFailureHandler" ref="failureRedirectHandler"/>
</b:bean>

<!-- Logout handler terminating local session -->
<b:bean id="logoutHandler"
      class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
    <b:property name="invalidateHttpSession" value="false"/>
</b:bean>

<!-- Override default logout processing filter with the one processing SAML messages -->
<b:bean id="samlLogoutFilter" class="org.springframework.security.saml.SAMLLogoutFilter">
    <b:constructor-arg index="0" ref="successLogoutHandler"/>
    <b:constructor-arg index="1" ref="logoutHandler"/>
    <b:constructor-arg index="2" ref="logoutHandler"/>
</b:bean>

<!-- Filter processing incoming logout messages -->
<!-- First argument determines URL user will be redirected to after successful global logout -->
<b:bean id="samlLogoutProcessingFilter" class="org.springframework.security.saml.SAMLLogoutProcessingFilter">
    <b:constructor-arg index="0" ref="successLogoutHandler"/>
    <b:constructor-arg index="1" ref="logoutHandler"/>
</b:bean>

<!-- Class loading incoming SAML messages from httpRequest stream -->
<b:bean id="processor" class="org.springframework.security.saml.processor.SAMLProcessorImpl">
    <b:constructor-arg>
        <b:list>
            <b:ref bean="redirectBinding"/>
            <b:ref bean="postBinding"/>
            <b:ref bean="artifactBinding"/>
            <b:ref bean="soapBinding"/>
            <b:ref bean="paosBinding"/>
        </b:list>
    </b:constructor-arg>
</b:bean>

<!-- SAML 2.0 WebSSO Assertion Consumer -->
<b:bean id="webSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerImpl"/>

<!-- SAML 2.0 Holder-of-Key WebSSO Assertion Consumer -->
<b:bean id="hokWebSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl"/>

<!-- SAML 2.0 Web SSO profile -->
<b:bean id="webSSOprofile" class="org.springframework.security.saml.websso.WebSSOProfileImpl"/>

<!-- SAML 2.0 Holder-of-Key Web SSO profile -->
<b:bean id="hokWebSSOProfile" class="org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl"/>

<!-- SAML 2.0 ECP profile -->
<b:bean id="ecpprofile" class="org.springframework.security.saml.websso.WebSSOProfileECPImpl"/>

<!-- SAML 2.0 Logout Profile -->
<b:bean id="logoutprofile" class="org.springframework.security.saml.websso.SingleLogoutProfileImpl"/>

<!-- Bindings, encoders and decoders used for creating and parsing messages -->
<b:bean id="postBinding" class="org.springframework.security.saml.processor.HTTPPostBinding">
    <b:constructor-arg ref="parserPool"/>
    <b:constructor-arg ref="velocityEngine"/>
</b:bean>

<b:bean id="redirectBinding" class="org.springframework.security.saml.processor.HTTPRedirectDeflateBinding">
    <b:constructor-arg ref="parserPool"/>
</b:bean>

<b:bean id="artifactBinding" class="org.springframework.security.saml.processor.HTTPArtifactBinding">
    <b:constructor-arg ref="parserPool"/>
    <b:constructor-arg ref="velocityEngine"/>
    <b:constructor-arg>
        <b:bean class="org.springframework.security.saml.websso.ArtifactResolutionProfileImpl">
            <b:constructor-arg>
                <b:bean class="org.apache.commons.httpclient.HttpClient">
                    <b:constructor-arg>
                        <b:bean class="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"/>
                    </b:constructor-arg>
                </b:bean>
            </b:constructor-arg>
            <b:property name="processor">
                <b:bean class="org.springframework.security.saml.processor.SAMLProcessorImpl">
                    <b:constructor-arg ref="soapBinding"/>
                </b:bean>
            </b:property>
        </b:bean>
    </b:constructor-arg>
</b:bean>

<b:bean id="soapBinding" class="org.springframework.security.saml.processor.HTTPSOAP11Binding">
    <b:constructor-arg ref="parserPool"/>
</b:bean>

<b:bean id="paosBinding" class="org.springframework.security.saml.processor.HTTPPAOS11Binding">
    <b:constructor-arg ref="parserPool"/>
</b:bean>

<!-- Initialization of OpenSAML library-->
<b:bean class="org.springframework.security.saml.SAMLBootstrap"/>

<!-- Initialization of the velocity engine -->
<b:bean id="velocityEngine" class="org.springframework.security.saml.util.VelocityFactory" factory-method="getEngine"/>

<!-- XML parser pool needed for OpenSAML parsing -->
<b:bean id="parserPool" class="org.opensaml.xml.parse.StaticBasicParserPool" init-method="initialize">
    <b:property name="builderFeatures">
        <b:map>
            <b:entry key="http://apache.org/xml/features/dom/defer-node-expansion" value="false"/>
        </b:map>
    </b:property>
</b:bean>

<b:bean id="parserPoolHolder" class="org.springframework.security.saml.parser.ParserPoolHolder"/>

【问题讨论】:

【参考方案1】:

我终于找到了解决方案,可以访问 Discovery URL(返回错误,但会是another question)。

对我的问题的解释是属性:

<b:property name="idpDiscoveryEnabled" value="true"/>
<b:property name="idpDiscoveryURL" value="https://discovery.renater.fr/test"/>

必须在 IDP 元数据中设置(在注释为“地图中未指定实体的默认扩展元数据”的部分中),而不是像我所做的那样在 SP 元数据中设置。

我必须在Spring Security SAML's repository on GitHub 中搜索字符串“idpDiscoveryEnabled”以找出我做错了什么。

我会建议 Vladimír Schäfer 在 Spring Security SAML 的文档中添加更多关于此的信息。因为在 SP 和 IDP 配置中都使用了元数据,所以很容易混合主题并且不会意识到我们在错误的 bean 中设置了属性。

更新:我意识到文档提供的信息可能有误:

远程发现服务

为了启用外部 IDP 发现服务,请将 本地 SP 扩展元数据 中的属性 idpDiscoveryURL 配置为 外部发现 URL。确保设置了属性 idpDiscoveryEnabledtrue。远程发现服务需要支持 Identity 提供者发现服务协议和配置文件。

根据我的经验,粗体部分可能是错误的。

【讨论】:

感谢您解决此 Grégoire 问题,我会看看。 @VladimírSchäfer :如果您有几分钟的时间,我将非常感谢您对此的帮助:***.com/q/33943499/1528942

以上是关于如何使用 Spring Security SAML 配置远程发现?的主要内容,如果未能解决你的问题,请参考以下文章

如何配置 Spring Security SAML 以与 Okta 一起使用?

spring-security saml2:如何获取当前用户?

如何使用 Spring Security SAML 配置远程发现?

如何在同一应用程序中使用 spring-sample 示例配置 Spring Security 基本身份验证和 SAML 身份验证

spring security saml使用哪个密钥?

使用 Spring Security saml 的 IDP 会话超时