Spring SAML - 在运行时读取和刷新 IdP 元数据
Posted
技术标签:
【中文标题】Spring SAML - 在运行时读取和刷新 IdP 元数据【英文标题】:Spring SAML - Reading and refreshing IdP metadata at runtime 【发布时间】:2017-08-11 22:20:31 【问题描述】:我正在使用带有 Spring-SAML 扩展的 WSO2 和 SSOCircle。我们此时正在测试配置,并在我们的 applicationContext 中定义了 2 个 IdP 和 2 个 SP。因此,目前,我们的 spring xml 配置中有 2 个静态定义的 IdP,这是有效的。出于测试目的,我们使用 CachingMetadataManager 和 ResourceBackedMetadataProvider 的组合,因此 IdP 元数据构建在我们的 WAR 存档中。示例:
<bean id="metadata" class="org.springframework.security.saml.metadata.CachingMetadataManager">
<constructor-arg>
<list>
<bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
<constructor-arg>
<bean class="org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider">
<constructor-arg>
<bean class="java.util.Timer"/>
</constructor-arg>
<constructor-arg>
<bean class="org.opensaml.util.resource.ClasspathResource">
<constructor-arg value="/metadata/wso2idp_metadata.xml"/>
</bean>
</constructor-arg>
<property name="parserPool" ref="parserPool"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
</bean>
</constructor-arg>
</bean>
<bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
<constructor-arg>
<bean class="org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider">
<constructor-arg>
<bean class="java.util.Timer"/>
</constructor-arg>
<constructor-arg>
<bean class="org.opensaml.util.resource.ClasspathResource">
<constructor-arg value="/metadata/ssocircleidp_metadata.xml"/>
</bean>
</constructor-arg>
<property name="parserPool" ref="parserPool"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
</bean>
</constructor-arg>
</bean>
</list>
</constructor-arg>
对于生产,我们希望能够将 IdP 元数据存储在数据库中(位于中心位置)。我希望能够在不重新部署 WAR 或重新启动服务器的情况下添加、删除和修改元数据。最初,我认为我可以覆盖 CachingMetadataManager 并定义一个 noarg 构造函数,该构造函数可以动态加载所有元数据提供程序,但这是不可能的,因为 CachingMetadataManager 只定义了一个必须接受 MetadataProvider 列表的构造函数。我最终做了以下事情:
<bean id="metadataList" class="org.arbfile.util.security.saml.DBMetadataProviderList">
<constructor-arg ref="parserPool" />
<constructor-arg>
<bean class="java.util.Timer"/>
</constructor-arg>
</bean>
<bean id="metadata" class="org.springframework.security.saml.metadata.CachingMetadataManager">
<constructor-arg ref="metadataList" />
</bean>
Bean metadataList 可以简单定义为:
public final class DBMetadataProviderList extends ArrayList<MetadataProvider>
private final static Logger log = LoggerFactory.getLogger(DBMetadataProviderList.class);
private ParserPool parser;
public DBMetadataProviderList(ParserPool _parser, Timer _timer) throws MetadataProviderException
this.parser = _parser;
// Lookup metadata from DB
这确实允许我动态读取 IdP 元数据。不过,当谈到刷新时,我的逻辑就崩溃了。我找到了this post on the spring forum,但它已经 3 到 4 岁了。动态读取、添加和更新 IdP 元数据、对其进行缓存并在某个时间间隔刷新缓存的最佳方式是什么?在我的情况下,数据库表中的 1 行将等同于单个 IdP 元数据定义。
【问题讨论】:
嗨吉姆,你能提供一个更新,你是怎么得到的?使用下面的示例?我还需要实现这个场景:)。 【参考方案1】:在阅读了几篇文章并扫描了源代码后,我发现这个问题的答案比我想象的要复杂。实际上有 3 种不同的场景需要解决。
-
从数据库表中初始读取所有 IdP 元数据提供者
过期并重新读取 IdP 元数据 XML 数据
无需更改配置或重新启动服务器即可动态添加和删除提供程序
我将一次处理其中的每一项。第 1 项:可能有几种方法可以解决此问题,但请查看上面的 DBMetadataProviderList
类(在我的 OP 中)作为快速而肮脏的解决方案。下面是更完整的构造函数代码:
//This constructor allows us to read in metadata stored in a database.
public DBMetadataProviderList(ParserPool _parser, Timer _timer) throws MetadataProviderException
this.parser = _parser;
List<String> metadataProviderIds = getUniqueEntityIdListFromDB();
for (final String mdprovId : metadataProviderIds)
DBMetadataProvider metadataProvider = new DBMetadataProvider(_timer, mdprovId);
metadataProvider.setParserPool(this.parser);
metadataProvider.setMaxRefreshDelay(480000); // 8 mins (set low for testing)
metadataProvider.setMinRefreshDelay(120000); // 2 mins
ExtendedMetadataDelegate md = new ExtendedMetadataDelegate(metadataProvider, new ExtendedMetadata());
add(md);
为了解决第 2 项问题,我使用 FilesystemMetadataProvider
作为指导并创建了一个 DBMetadataProvider
类。通过扩展AbstractReloadingMetadataProvider
类并实现fetchMetadata()
方法,我们有内置的缓存刷新,这要归功于opensaml。以下是重要部分(仅示例代码):
public class DBMetadataProvider extends AbstractReloadingMetadataProvider
private String metaDataEntityId; // unique Id for DB lookups
/**
* Constructor.
* @param entityId the entity Id of the metadata. Use as key to identify a database row.
*/
public DBMetadataProvider(String entityId)
super();
setMetaDataEntityId(entityId);
/**
* Constructor.
* @param backgroundTaskTimer timer used to refresh metadata in the background
* @param entityId the entity Id of the metadata. Use as key to identify a database row.
*/
public DBMetadataProvider(Timer backgroundTaskTimer, String entityId)
super(backgroundTaskTimer);
setMetaDataEntityId(entityId);
public String getMetaDataEntityId() return metaDataEntityId;
public void setMetaDataEntityId(String metaDataEntityId) this.metaDataEntityId = metaDataEntityId;
@Override
protected String getMetadataIdentifier() return getMetaDataEntityId();
// This example code simply does straight JDBC
@Override
protected byte[] fetchMetadata() throws MetadataProviderException
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try
conn = JDBCUtility.getConnection();
ps = conn.prepareStatement("select bla bla bla ENTITY_ID = ?");
ps.setString(1, getMetaDataEntityId());
rs = ps.executeQuery();
if (rs.next())
// include a modified date column in schema so that we know if row has changed
Timestamp sqldt = rs.getTimestamp("MOD_DATE"); // use TimeStamp here to get full datetime
DateTime metadataUpdateTime = new DateTime(sqldt.getTime(), ISOChronology.getInstanceUTC());
if (getLastRefresh() == null || getLastUpdate() == null || metadataUpdateTime.isAfter(getLastRefresh()))
log.info("Reading IdP metadata from database with entityId = " + getMetaDataEntityId());
Clob clob = rs.getClob("XML_IDP_METADATA");
return clob2Bytes(clob);
return null;
else
// row was not found
throw new MetadataProviderException("Metadata with entityId = '" + getMetaDataEntityId() + "' does not exist");
catch (Exception e)
String msg = "Unable to query metadata from database with entityId = " + getMetaDataEntityId();
log.error(msg, e);
throw new MetadataProviderException(msg, e);
finally
// clean up connections
resource 帮助我找到了缓存重新加载元数据提供程序类的正确技术。最后,#3 可以通过实现这个post来解决。
【讨论】:
能否为第三点分享一个例子。我关注了这篇文章:docs.spring.io/autorepo/docs/spring-security-saml/1.0.0.RELEASE/…,但在删除元数据生成器过滤器后我被困在做什么。以上是关于Spring SAML - 在运行时读取和刷新 IdP 元数据的主要内容,如果未能解决你的问题,请参考以下文章
如何在运行时在 spring-SAML 中添加新的 idp 元数据
ADFS spring-saml 依赖方没有配置 AssertionConsumerService