如何在多租户环境中自动选择配置的 SAML 身份提供程序以使用 Spring SAML 进行 SSO

Posted

技术标签:

【中文标题】如何在多租户环境中自动选择配置的 SAML 身份提供程序以使用 Spring SAML 进行 SSO【英文标题】:How do I automatically pick the configured SAML Identity provider in a multi-tenant environment to do SSO using Spring SAML 【发布时间】:2015-03-19 07:44:18 【问题描述】:

我在多租户应用程序中使用 Spring SAML 来提供 SSO。不同的租户使用不同的 url 访问应用程序,并且每个租户都配置了单独的 Identity Provider。给定用于访问应用程序的 url,如何自动分配正确的身份提供者?

例子:

租户 1:http://tenant1.myapp.com

租户 2:http://tenant2.myapp.com

我看到我可以将参数 idp 添加到 url (http://tenant1.myapp.com?idp=my.idp.entityid.com) 并且 SAMLContextProvider 将选择具有该实体 id 的身份提供者。我开发了一个数据库支持的 MetadataProvider,它将租户主机名作为初始化参数,以从链接到该主机名的数据库中获取该租户的元数据。现在我想我需要一些方法来遍历元数据提供者以将元数据的 entityId 链接到主机名。不过,我看不到如何获取元数据的 entityId。这将解决我的问题。

【问题讨论】:

【参考方案1】:

您可以在方法MetadataManager#parseProvider 中查看如何从MetadataProvider 中解析可用的实体ID。请注意,通常每个提供商都可以提供多个 IDP 和 SP 定义,而不仅仅是一个。

或者,您可以使用您自己的类进一步扩展 ExtendedMetadataDelegate,包含您希望的任何其他元数据(如 entityId),然后只需将 MetadataProvider 重新键入您的自定义类,并在通过MetadataManager.

如果我是你,我会采取一些不同的方法。我会扩展SAMLContextProviderImpl,覆盖方法populatePeerEntityId 并在那里执行主机名/IDP 的所有匹配。详情请见original method。

【讨论】:

我创建了自己的 SAMLContextProvider 并覆盖了 populatePeerIdentityId。那效果很好。完成后,我意识到 SAMLContextProvider 仅在 SP 启动的 SSO 期间使用。我们主要使用 IDP 发起的 SSO,所以我也需要介绍这一点。我最终检查了传入消息的 peerEntityID 与在我的自定义 SAMLAuthenticationProvider 中为该租户配置的 IDP entityID。 这个将身份提供者映射到服务提供者的特性是支持多租户的关键。这是否计划在即将发布的版本中? 我们会看到,这个项目取决于我的空闲时间(它不是由任何人赞助的)而且没有太多。改善多租户是我想要完成的事情。【参考方案2】:

在撰写本文时,Spring SAML 的版本为 1.0.1.FINAL。它不支持开箱即用的多租户。除了上述 Vladimir 给出的建议之外,我还找到了另一种实现多租户的方法。它非常简单直接,不需要扩展任何 Spring SAML 类。此外,它利用 Spring SAML 对 CachingMetadataManager 中别名的内置处理。

在您的控制器中,从请求中捕获租户名称,并使用租户名称作为别名创建一个ExtendedMetadata 对象。接下来从ExtendedMetadata 中创建一个ExtendedMetadataDelegate 并对其进行初始化。从中解析实体 id 并检查它们是否存在于MetadataManager 中。如果它们不存在,请添加提供程序并刷新元数据。然后使用getEntityIdForAlias()MetadataManager获取实体ID。

这是控制器的代码。有 cmets inline 解释了一些注意事项:

@Controller
public class SAMLController 

    @Autowired
    MetadataManager metadataManager;

    @Autowired
    ParserPool parserPool;

    @RequestMapping(value = "/login.do", method = RequestMethod.GET)
    public ModelAndView login(HttpServletRequest request, HttpServletResponse response, @RequestParam String tenantName)
                                                        throws MetadataProviderException, ServletException, IOException
        //load metadata url using tenant name
        String tenantMetadataURL = loadTenantMetadataURL(tenantName);

        //Deprecated constructor, needs to change
        HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(tenantMetadataURL, 15000);
        httpMetadataProvider.setParserPool(parserPool);

        //Create extended metadata using tenant name as the alias
        ExtendedMetadata metadata = new ExtendedMetadata();
        metadata.setLocal(true);
        metadata.setAlias(tenantName);

        //Create metadata provider and initialize it
        ExtendedMetadataDelegate metadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider, metadata);
        metadataDelegate.initialize();

        //getEntityIdForAlias() in MetadataManager must only be called after the metadata provider
        //is added and the metadata is refreshed. Otherwise, the alias will be mapped to a null
        //value. The following code is a roundabout way to figure out whether the provider has already
        //been added or not. 

        //The method parseProvider() has protected scope in MetadataManager so it was copied here         
        Set<String> newEntityIds = parseProvider(metadataDelegate);
        Set<String> existingEntityIds = metadataManager.getIDPEntityNames();

        //If one or more IDP entity ids do not exist in metadata manager, assume it's a new provider.
        //If we always add a provider without this check, the initialize methods in refreshMetadata()
        //ignore the provider in case of a duplicate but the duplicate still gets added to the list
        //of providers because of the call to the superclass method addMetadataProvider(). Might be a bug.
        if(!existingEntityIds.containsAll(newEntityIds)) 
            metadataManager.addMetadataProvider(metadataDelegate);
            metadataManager.refreshMetadata();
        

        String entityId = metadataManager.getEntityIdForAlias(tenantName);

        return new ModelAndView("redirect:/saml/login?idp=" + URLEncoder.encode(entityId, "UTF-8"));
    

    private Set<String> parseProvider(MetadataProvider provider) throws MetadataProviderException 
        Set<String> result = new HashSet<String>();

        XMLObject object = provider.getMetadata();
        if (object instanceof EntityDescriptor) 
            addDescriptor(result, (EntityDescriptor) object);
         else if (object instanceof EntitiesDescriptor) 
            addDescriptors(result, (EntitiesDescriptor) object);
        

        return result;

    

    private void addDescriptors(Set<String> result, EntitiesDescriptor descriptors) throws MetadataProviderException 
        if (descriptors.getEntitiesDescriptors() != null) 
            for (EntitiesDescriptor descriptor : descriptors.getEntitiesDescriptors()) 
                addDescriptors(result, descriptor);
            
        

        if (descriptors.getEntityDescriptors() != null) 
            for (EntityDescriptor descriptor : descriptors.getEntityDescriptors()) 
                addDescriptor(result, descriptor);
            
        
    

    private void addDescriptor(Set<String> result, EntityDescriptor descriptor) throws MetadataProviderException 
        String entityID = descriptor.getEntityID();
        result.add(entityID);
    

我相信这直接解决了 OP 找出如何为给定租户获取 IDP 的问题。但这仅适用于具有单个实体 ID 的 IDP。

【讨论】:

只想指出,除非您的用户有粘性会话,否则此解决方案在集群环境中不起作用......对 /login.do 的初始请求将元数据提供程序添加到与该请求关联的 JVM,但是用户可以在另一个不知道启动身份验证过程的 IDP 的 JVM 上返回应用程序...

以上是关于如何在多租户环境中自动选择配置的 SAML 身份提供程序以使用 Spring SAML 进行 SSO的主要内容,如果未能解决你的问题,请参考以下文章

使用 spring-SAML 在 Pentaho 中多租户 SSO 登录后重定向

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

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

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

在多租户 stup 中使用 Spring Security OAuth 动态注册 OIDC 客户端

如何搭建一套Azure AD与ADFS集成身份的环境