How do I automatically pick the configured SAML Identity provider in a multi-tenant environment to do SSO using Spring SAML

回眸只為那壹抹淺笑 提交于 2019-12-18 16:56:45

问题


I am using Spring SAML in a multi-tenant application to provide SSO. Different tenants use different urls to access the application, and each has a separate Identity Provider configured. How do I automatically assign the correct Identity Provider given the url used to access the application?

Example:

Tenant 1: http://tenant1.myapp.com

Tenant 2: http://tenant2.myapp.com

I saw that I can add a parameter idp to the url (http://tenant1.myapp.com?idp=my.idp.entityid.com) and the SAMLContextProvider will pick the identity provider with that entity id. I developed a database-backed MetadataProvider that takes the tenant hostname as initialisation parameter to fetch the metadata for that tenant form the database linked to that hostname. Now I think I need some way to iterate over the metadata providers to link entityId of the metadata to the hostname. I don't see how I can fetch the entityId of the metadata, though. That would solve my problem.


回答1:


You can see how to parse available entityIDs out of a MetadataProvider in method MetadataManager#parseProvider. Note that generally each provider can supply multiple IDP and SP definitions, not just one.

Alternatively, you could further extend the ExtendedMetadataDelegate with your own class, include whatever additional metadata (like entityId) you wish, and then simply retype MetadataProvider to your customized class and get information from there when iterating data through the MetadataManager.

If I were you, I'd take a little bit different approach though. I would extend SAMLContextProviderImpl, override method populatePeerEntityId and perform all the matching of hostname/IDP there. See the original method for details.




回答2:


At the time of writing, Spring SAML is at version 1.0.1.FINAL. It does not support multi-tenancy cleanly out of the box. I found another way to achieve multi-tenancy apart from the suggestions given by Vladimir above. It's very simple and straight-forward and does not require extension of any Spring SAML classes. Furthermore, it utilizes Spring SAML's in-built handling of aliases in CachingMetadataManager.

In your controller, capture the tenant name from the request and create an ExtendedMetadata object using the tenant name as the alias. Next create an ExtendedMetadataDelegate out of the ExtendedMetadata and initialize it. Parse the entity ids out of it and check if they exist in MetadataManager. If they don't exist, add the provider and refresh metadata. Then get the entity id from MetadataManager using getEntityIdForAlias().

Here is the code for the controller. There are comments inline explaining some caveats:

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

I believe this directly solves the OP's problem of figuring out how to get the IDP for a given tenant. But this will work only for IDPs with a single entity id.



来源:https://stackoverflow.com/questions/28056206/how-do-i-automatically-pick-the-configured-saml-identity-provider-in-a-multi-ten

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!