我在多租户应用程序中使用Spring SAML来提供SSO。不同的租户使用不同的URL来访问应用程序,并且每个租户都配置了单独的身份提供程序。在给定用于访问应用程序的URL的情况下,如何自动分配正确的身份提供者?
示例:
我看到我可以在url(http://tenant1.myapp.com?idp=my.idp.entityid.com)中添加参数idp,SAMLContextProvider将选择具有该实体ID的身份提供者。我开发了一个数据库支持的MetadataProvider,它将租户主机名作为初始化参数,从该主机中获取链接到该主机名的数据库的元数据。现在我想我需要一些方法来迭代元数据提供程序,以将元数据的entityId链接到主机名。但是,我不知道如何获取元数据的entityId。这将解决我的问题。
答案 0 :(得分:6)
您可以在方法MetadataManager#parseProvider
中查看如何解析MetadataProvider
中的可用实体ID。请注意,通常每个提供商都可以提供多个IDP和SP定义,而不仅仅是一个。
或者,您可以使用自己的类进一步扩展ExtendedMetadataDelegate
,包含您希望的任何其他元数据(如entityId),然后只需将MetadataProvider
重新键入您的自定义类,并从那里获取信息通过MetadataManager
迭代数据。
如果我是你,我会采取一些不同的方法。我会扩展SAMLContextProviderImpl
,覆盖方法populatePeerEntityId
并在那里执行主机名/ IDP的所有匹配。有关详细信息,请参阅original method。
答案 1 :(得分:3)
在撰写本文时,Spring SAML的版本为1.0.1.FINAL。它不支持干净利落的多租户。除了上面弗拉基米尔提出的建议外,我找到了实现多租户的另一种方式。它非常简单直接,不需要扩展任何Spring SAML类。此外,它利用Spring SAML在CachingMetadataManager
中内置的别名处理。
在您的控制器中,从请求中捕获租户名称,并使用租户名称作为别名创建ExtendedMetadata
对象。接下来,在ExtendedMetadataDelegate
中创建ExtendedMetadata
并初始化它。从中解析实体ID并检查它们是否存在于MetadataManager
中。如果它们不存在,请添加提供程序并刷新元数据。然后使用MetadataManager
从getEntityIdForAlias()
获取实体ID。
这是控制器的代码。有评论内联解释一些警告:
@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。