在多租户环境中,如何在运行时在不同的 url(子域)上为不同的服务提供者提供不同的元数据?

Posted

技术标签:

【中文标题】在多租户环境中,如何在运行时在不同的 url(子域)上为不同的服务提供者提供不同的元数据?【英文标题】:In an multi-tenant enviroment how can I provide different metadata for different Service Providers at runtime on different urls (subdomains)? 【发布时间】:2016-05-05 19:24:48 【问题描述】:

使用 SP 发起的单点登录 (SSO) 与 SP 和 IdP 都是自托管的,因此可以灵活地编辑两者。我正在使用 spring-security-saml2-core-1.0.1.RELEASE 来托管 spring 应用程序(spring-security-3.2.8,spring-mvc-3.2.14.RELEASE),它为 url 上的多个租户提供服务说: sp1.example.org,sp2.example.org IdP 使用 Shibboleth IdPv3.2.1 托管,可与托管在不同 SP 服务器上的多个应用程序正常工作。

我正在尝试从同一服务器为 sp1 和 sp2 发送不同的元数据。我通过覆盖 SAMLContextProviderImpl populatePeerEntityId 阅读了关于多租户 SP here 和 here 的自定义逻辑,同样我试图覆盖 populateLocalEntityId,因为我不能使用 alias

有人可以提供一个示例代码来覆盖populateLocalEntityId 以处理多租户元数据吗?

SP配置如下图:

<!-- Filters for processing of SAML messages -->
<beans: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>
</beans:bean>

<!-- Handler deciding where to redirect user after successful login -->
<beans:bean id="successRedirectHandler" class="com.example.web.sso.CustomAuthenticationSuccessHandler" ></beans:bean>
<!-- <beans:bean id="successRedirectHandler" 
        class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
   <property name="defaultTargetUrl" value="/WEB-INF/security/idpSelection.jsp"/>
</beans:bean>  --> 


<!-- Use the following for interpreting RelayState coming from unsolicited 
    response as redirect URL: 
    <beans:bean id="successRedirectHandler" class="org.springframework.security.saml.SAMLRelayStateSuccessHandler"> 
    <property name="defaultTargetUrl" value="/" /> </beans:bean> -->

<!-- Handler deciding where to redirect user after failed login -->
<beans:bean id="failureRedirectHandler" class="com.example.web.sso.CustomAuthenticationFailureHandler"></beans:bean>

<!-- <beans:bean id="failureRedirectHandler"
    class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
    <property name="useForward" value="true" />
    <property name="defaultFailureUrl" value="/error.jsp" />
</beans:bean>  -->

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

<authentication-manager alias="samlauthenticationManager">
    <!-- Register authentication manager for SAML provider -->
    <authentication-provider ref="samlAuthenticationProvider" />
    <!-- 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 -->
<beans:bean id="samlLogger" class="org.springframework.security.saml.log.SAMLDefaultLogger" >
    <beans:property name="logMessages" value="true" />
    <beans:property name="logErrors" value="true" />
</beans:bean>

<!-- Central storage of cryptographic keys -->
<beans:bean id="keyManager" class="org.springframework.security.saml.key.JKSKeyManager">

    <beans:constructor-arg value="/WEB-INF/keys/samlKeystore.jks"></beans:constructor-arg>
    <beans:constructor-arg type="java.lang.String" value="nalle123" />
    <beans:constructor-arg>
        <beans:map>
            <beans:entry key="apollo" value="nalle123" />
        </beans:map>
    </beans:constructor-arg>
    <beans:constructor-arg type="java.lang.String" value="apollo" />
</beans:bean>

<!-- Entry point to initialize authentication, default values taken from 
    properties file -->
<beans:bean id="samlEntryPoint" class="com.example.web.sso.CustomSAMLEntryPoint">
    <beans:property name="defaultProfileOptions">
        <beans:bean class="org.springframework.security.saml.websso.WebSSOProfileOptions">
            <beans:property name="binding" value="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"/>
            <beans:property name="nameID" value="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" />
            <beans:property name="includeScoping" value="false" />
            <beans:property name="forceAuthN" value="false" />
        </beans:bean>
    </beans:property>
</beans:bean>

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

<!-- Filter automatically generates default SP metadata --> 
<beans:bean id="metadataGeneratorFilter"
    class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
    <beans:constructor-arg>
        <beans:bean class="org.springframework.security.saml.metadata.MetadataGenerator">
            <beans:property name="entityId" value="com:example:namespaceId:saml:poc" />
            <!-- <beans:property name="entityBaseURL" value="https://sp1.example.com:8080/" /> -->
            <beans:property name="requestSigned" value="true" />
            <beans:property name="wantAssertionSigned" value="true" />
            <beans:property name="extendedMetadata">
                <beans:bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
                    <beans:property name="idpDiscoveryEnabled" value="true" />
                </beans:bean>
            </beans:property>
        </beans:bean>
    </beans:constructor-arg>
</beans:bean>

<beans:bean id="metadataGenerator" class="org.springframework.security.saml.metadata.MetadataGenerator">
            <beans:property name="entityId" value="com:example:namespaceId:saml:poc" />
            <beans:property name="entityBaseURL" value="https://sp1.example.com:8080/" />
            <beans:property name="requestSigned" value="true" />
            <beans:property name="wantAssertionSigned" value="true" />
            <beans:property name="extendedMetadata">
                <beans:bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
                    <beans:property name="idpDiscoveryEnabled" value="true" />
                </beans:bean>
            </beans:property>
        </beans:bean>

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

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

<!-- IDP Metadata configuration - paths to metadata of IDPs in circle of 
    trust is here -->
<beans:bean id="metadata"
    class="org.springframework.security.saml.metadata.CachingMetadataManager">
    <beans:constructor-arg>
        <beans:list>
            <!-- Example of classpath metadata with Extended Metadata -->
            <beans:bean
                class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
                <beans:constructor-arg>
                    <beans:bean
                        class="org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider">
                        <beans:constructor-arg>
                            <beans:bean class="java.util.Timer" />
                        </beans:constructor-arg>
                        <beans:constructor-arg>
                            <beans:bean class="org.opensaml.util.resource.ClasspathResource">
                            <!-- <beans:bean class="org.opensaml.util.resource.FilesystemResource"> -->
                                    <beans:constructor-arg value = "/WEB-INF/metadata/sp1-mymetadata.xml"></beans:constructor-arg>
                            </beans:bean>
                        </beans:constructor-arg>
                        <beans:property name="parserPool" ref="parserPool" />
                    </beans:bean>
                </beans:constructor-arg>
                <beans:constructor-arg>

                    <beans:bean
                        class="org.springframework.security.saml.metadata.ExtendedMetadata">
                        <beans:property name="local" value="true" />
                        <beans:property name="securityProfile" value="metaiop" />
                        <beans:property name="sslSecurityProfile" value="pkix" />
                        <beans:property name="sslHostnameVerification" value="default" />
                        <!-- <beans:property name="sslHostnameVerification" value="allowAll" /> -->
                        <beans:property name="signMetadata" value="false" />
                        <beans:property name="signingKey" value="apollo" />
                        <beans:property name="encryptionKey" value="apollo" />
                        <beans:property name="requireArtifactResolveSigned" value="false" />
                        <beans:property name="requireLogoutRequestSigned" value="false" />
                        <beans:property name="requireLogoutResponseSigned" value="false" />
                        <beans:property name="idpDiscoveryEnabled" value="false" />
                        <beans:property name="idpDiscoveryURL" value="https://sp1.example.com/saml/discovery" />
                        <beans:property name="idpDiscoveryResponseURL" value="https://sp1.example.com/saml/login?disco=true" />
                    </beans:bean>
                </beans:constructor-arg>

            </beans:bean>
            <!-- Example of HTTP metadata without Extended Metadata -->
            <!-- <beans:bean class="org.opensaml.saml2.metadata.provider.HTTPMetadataProvider">
                URL containing the metadata
                <beans:constructor-arg>
                    <beans:value type="java.lang.String">https://idp.ssocircle.com/idp-meta.xml</beans:value>
                    <beans:value type="java.lang.String">https://sp1.example.com/idp-meta.xml</beans:value>
                </beans:constructor-arg>
                Timeout for metadata loading in ms
                <beans:constructor-arg>
                    <beans:value type="int">15000</beans:value>
                </beans:constructor-arg>
                <beans:property name="parserPool" ref="parserPool" />
            </beans:bean> -->

            <beans:bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
                <beans:constructor-arg>
                    <beans:bean class="org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider">
                        <beans:constructor-arg>
                            <beans:value type="java.io.File">/shared/saml/idp-metadata-exampleIdp.xml</beans:value>
                        </beans:constructor-arg>
                        <beans:property name="parserPool" ref="parserPool"/>
                    </beans:bean>
                </beans:constructor-arg>
                <beans:constructor-arg>
                    <beans:bean class="org.springframework.security.saml.metadata.ExtendedMetadata"/>
                </beans:constructor-arg>
            </beans:bean>

            <!-- Example of file system metadata without Extended Metadata -->
            <!-- <bean class="org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider"> 
                <constructor-arg> <value type="java.io.File">/usr/local/metadata/idp.xml</value> 
                </constructor-arg> <property name="parserPool" ref="parserPool"/> </bean> -->
        </beans:list>
    </beans:constructor-arg>
    <!-- OPTIONAL used when one of the metadata files contains information 
        about this service provider -->
    <!-- <property name="hostedSPName" value=""/> -->
    <!-- OPTIONAL property: can tell the system which IDP should be used for 
        authenticating user by default. -->
    <!-- <property name="defaultIDP" value="http://localhost:8080/opensso"/> -->
    <beans:property name="defaultIDP" value="https://login.example.com/idp/shibboleth"/>
</beans:bean>

<!-- SAML Authentication Provider responsible for validating of received 
    SAML messages -->
<beans:bean id="samlAuthenticationProvider"
    class="org.springframework.security.saml.SAMLAuthenticationProvider">
    <!-- OPTIONAL property: can be used to store/load user data after login -->
    <beans:property name="userDetails" ref="sAMLUserDetailsServiceImpl" />
    <beans:property name="forcePrincipalAsString" value="false" />
</beans:bean>

<beans:bean id="sAMLUserDetailsServiceImpl"
    class="com.example.service.impl.SAMLUserDetailsServiceImpl"></beans:bean>

<!-- Provider of default SAML Context -->
<!-- <beans:bean id="contextProvider"
    class="org.springframework.security.saml.context.SAMLContextProviderImpl"> -->
<beans:bean id="contextProvider"
    class="com.example.service.impl.CustomSAMLContextProviderImpl"> 
    <beans:property name="storageFactory">
        <!-- <beans:bean class="org.springframework.security.saml.storage.EmptyStorageFactory" /> -->
        <beans:bean class="org.springframework.security.saml.storage.HttpSessionStorageFactory" />
    </beans:property>
</beans:bean>

<!-- <beans:bean id="contextProvider"
    class="org.springframework.security.saml.context.SAMLContextProviderLB">
    <beans:property name="scheme" value="https" />
    <beans:property name="serverName" value="https://sp1.example.com" />
    <beans:property name="serverPort" value="443" />
    <beans:property name="includeServerPortInRequestURL" value="false" />
</beans:bean>  -->

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

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

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

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

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

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

<!-- SAML 2.0 WebSSO Assertion Consumer -->
<beans:bean id="webSSOprofileConsumer"
    class="org.springframework.security.saml.websso.WebSSOProfileConsumerImpl" >
    <!-- maximum lifetime of assertion issued by Idp default 3000-->
    <!-- <beans:property name="maxAssertionTime" value="300"></beans:property>  -->
    <!-- maximum lifetime of authentication issued default 7200-->
    <!-- <beans:property name="maxAssertionTime" value="300"></beans:property>  -->
</beans:bean>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

【问题讨论】:

【参考方案1】:

更新 1:更好的解决方案是扩展 SAMLContextProviderImpl 并覆盖 populateLocalEntityId、getLocalEntity、getLocalAndPeerEntity,以便在为每个请求创建新的 SAMLMessageContext 时设置适当的 SAMLMessageContext。

@Override
public SAMLMessageContext getLocalAndPeerEntity(HttpServletRequest request, HttpServletResponse response) throws MetadataProviderException 

        SAMLMessageContext context = new SAMLMessageContext();
        populateGenericContext(request, response, context);
        //changed to send URL instead of URI
        populateLocalEntityId(context, request.getRequestURL().toString());
        populateLocalContext(context);
        populatePeerEntityId(context);
        populatePeerContext(context);
        return context;



@Override
public SAMLMessageContext getLocalEntity(HttpServletRequest request, HttpServletResponse response) throws MetadataProviderException 

        SAMLMessageContext context = new SAMLMessageContext();
        populateGenericContext(request, response, context);
        populateLocalEntityId(context, request.getRequestURL().toString());
        populateLocalContext(context);
        return context;



    @Override
    protected void populateLocalEntityId(SAMLMessageContext context, String requestURL) throws MetadataProviderException 

            String entityId;
            HTTPInTransport inTransport = (HTTPInTransport) context.getInboundMessageTransport();

            // Pre-configured entity Id
            entityId = (String) inTransport.getAttribute(org.springframework.security.saml.SAMLConstants.LOCAL_ENTITY_ID);
            if (entityId != null) 
                    // same code as super class

             else  // Defaults    
               //Now setting proper entityId as required
               //in this case https://sp1.wooqer.com/sp
               if(org.apache.commons.lang.StringUtils.ordinalIndexOf(requestURL, "/", 3) != -1) 
                        context.setLocalEntityId(requestURL.substring(0, org.apache.commons.lang.StringUtils.ordinalIndexOf(requestURL, "/", 3)).concat("/sp"));
                 else 
                        context.setLocalEntityId(requestURL.concat("/sp"));
                
                context.setLocalEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
            
    

在 MetadataGeneratorFilter 中设置 hostsSPName 不是一个好的解决方案,因为 @Autowired MetadataManager 必须设置在同步块下,以确保多个请求不会覆盖值。 MetadataManager 仍然可以在我们无法确定其状态的过滤器之外使用。 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~这可以通过创建一个联合来完成所有 SP(在这种情况下是在一个应用程序下运行的子域),然后在运行时选择适当的 entityID 和元数据。

我执行了以下步骤来处理此问题。因此,首先为联邦创建一个元数据,并将在这个应用程序上运行的所有子域 (SP) 添加到一个联邦中:

    <?xml version="1.0" encoding="UTF-8"?>
<EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:shibmd="urn:mace:shibboleth:metadata:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Name="https://example-federation.org/metadata/example-federation-name.xml">
   <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" ID="org_example_shagunakarsh_saml_poc_sp1" entityID="org:example:shagunakarsh:saml:poc:sp1">
      <!--other params-->
      ......
   </md:EntityDescriptor>
   <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="org_example_shagunakarsh_saml_poc_sp2" entityID="org:example:shagunakarsh:saml:poc:sp2">
      <!--other params-->
      ......
   </md:EntityDescriptor>
</EntitiesDescriptor>

现在我们需要在你的 metadata bean 中的 security-applicationContext.xml 中指定这个元数据文件:

<beans:bean id="metadata"
class="org.springframework.security.saml.metadata.CachingMetadataManager">
    .... 
    <beans:constructor-arg value = "/path/to/metadata/federation-mymetadata.xml"></beans:constructor-arg>

(阅读更新1,不建议为此目的覆盖过滤器)现在我们需要在运行时使用访问的URL选择合适的元数据,这可以通过扩展MetadataGeneratorFilter和覆盖processMetadataInitialization函数来实现:

    @Override
    protected void processMetadataInitialization(HttpServletRequest request) throws ServletException 

                    // In case the hosted SP metadata weren't initialized, let's do it now
                    if (manager.getHostedSPName() == null) 

                        synchronized (MetadataManager.class) 
                              //same code as Base Class
                        

                     else 
                            // if known SP is found from federation metadata
                            String requestURL = request.getRequestURL().toString();
                            String subDomain = requestURL.substring(requestURL.indexOf("//") + 2, requestURL.indexOf("."));
                            //set proper SP entityID
                            manager.setHostedSPName("org:example:shagunakarsh:saml:poc:" + subDomain);
                    
            

然后用这个 CustomMetadataGeneratorFilter 更新 security-applicationContext.xml:

<beans:bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.CustomMetadataGeneratorFilter">

不要忘记更新您的 IdP 中的元数据(本例为 Shibboleth IDPv3)。重新部署 IdP 和 SP,它应该可以工作。

【讨论】:

以上是关于在多租户环境中,如何在运行时在不同的 url(子域)上为不同的服务提供者提供不同的元数据?的主要内容,如果未能解决你的问题,请参考以下文章

Spring在多租户环境中为占位符配置application.properties

在多租户应用程序中如何检查应用程序在 Azure 门户中注册的租户数据?

运行工匠队列:在动态多租户多数据库系统上工作

如何在多租户应用程序中更新所有租户的所有架构?

Oracle12c多租户管理用户角色权限

如何在多租户应用程序的业务层中为可扩展的数据库结构设计实体?