2015-01-20 20 views
6

Sto utilizzando Spring SAML in un'applicazione multi-tenant per fornire SSO. Diversi titolari utilizzano URL diversi per accedere all'applicazione e ciascuno ha un Identity Provider separato configurato. Come si assegna automaticamente il provider di identità corretto in base all'URL utilizzato per accedere all'applicazione?Come selezionare automaticamente il provider di identità SAML configurato in un ambiente multi-tenant per eseguire SSO utilizzando Spring SAML

Esempio:

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

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

ho visto che posso aggiungere un parametro IDP all'URL (http://tenant1.myapp.com?idp=my.idp.entityid.com) e il SAMLContextProvider sceglierà i provider di identità con quell'ID entità. Ho sviluppato un MetadataProvider supportato da database che prende il nome host del tenant come parametro di inizializzazione per recuperare i metadati per quel tenant dal database collegato a tale nome host. Ora penso di aver bisogno di un modo per iterare sui provider di metadati per collegare entityId dei metadati al nome host. Non vedo come posso recuperare l'entità ID dei metadati, però. Questo risolverebbe il mio problema.

risposta

6

È possibile vedere come analizzare gli ID entità disponibili su un MetadataProvider nel metodo MetadataManager#parseProvider. Si noti che generalmente ogni fornitore può fornire più definizioni IDP e SP, non solo una.

In alternativa, si potrebbe estendere ulteriormente la ExtendedMetadataDelegate con la propria classe, includere qualunque metadati aggiuntivi (come EntityID) che si desidera, e poi semplicemente digitare nuovamente MetadataProvider alla classe personalizzata e ottenere informazioni da lì quando l'iterazione dei dati attraverso il MetadataManager.

Se fossi in te, avrei comunque un approccio un po 'diverso. Estenderei SAMLContextProviderImpl, sovrascrivi il metodo populatePeerEntityId ed eseguo tutte le corrispondenze di hostname/IDP lì. Vedere lo original method per i dettagli.

+3

Ho creato il mio SAMLContextProvider e ho scavalcato populatePeerIdentityId. Ha funzionato alla grande Una volta completato, mi sono reso conto che SAMLContextProvider viene utilizzato solo durante l'avvio SSO avviato da SP. Per la maggior parte usiamo SSO avviato da IDP, quindi ho dovuto occuparmene anch'io.Ho finito per controllare il peerEntityID del messaggio in arrivo con l'ID entità IDP configurato per quel tenant nel mio SAMLAuthenticationProvider personalizzato. – MarcFasel

+1

Questa funzionalità di associazione del provider di identità al provider di servizi è fondamentale per supportare la multi-tenancy. È programmato nelle prossime uscite? – MarcFasel

+0

Vedremo, il progetto dipende dal mio tempo libero (non è sponsorizzato da nessuno) e non c'è molto di questo. Migliorare la multi-tenancy è qualcosa che mi piacerebbe fare. –

3

Al momento della scrittura, Spring SAML è alla versione 1.0.1.FINAL. Non supporta la multi-tenancy in modo pulito e immediato. Ho trovato un altro modo per raggiungere la multi-tenancy oltre ai suggerimenti forniti da Vladimir sopra. È molto semplice e diretto e non richiede l'estensione di alcuna classe Spring SAML. Inoltre, utilizza la gestione incorporata degli alias di Spring SAML in CachingMetadataManager.

Nel controller, acquisire il nome del titolare dalla richiesta e creare un oggetto ExtendedMetadata utilizzando il nome del titolare come alias. Quindi creare uno ExtendedMetadataDelegate dal ExtendedMetadata e inizializzarlo. Analizza gli ID entità fuori da esso e controlla se esistono in MetadataManager. Se non esistono, aggiungere il provider e aggiornare i metadati. Quindi ottenere l'ID entità da MetadataManager utilizzando getEntityIdForAlias().

Ecco il codice per il controller. Ci sono commenti in linea che spiegano alcuni avvertimenti:

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

Credo che questo risolve direttamente il problema del PO di capire come ottenere l'IDP per un dato inquilino. Ma questo funzionerà solo per gli IDP con un singolo ID entità.

+0

Molto utile! Grazie per il tuo primo post! – Zarial

+0

Voglio solo sottolineare che questa soluzione non funziona in un ambiente cluster, a meno che tu non abbia sessioni appiccicose per i tuoi utenti .... La richiesta iniziale a /login.do aggiunge il provider di metadati alla JVM associata a tale richiesta, tuttavia l'utente può tornare all'app su un'altra JVM che non è a conoscenza dell'IDP che ha avviato il processo di autenticazione ... – danw