我正在使用WSO2和SSOCircle以及Spring-SAML扩展。我们目前正在测试配置,并在applicationContext中定义了2个IdP&2和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 List的构造函数。我最终做了以下事情:
<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元数据定义。
答案 0 :(得分:1)
在阅读了几篇帖子并扫描源代码之后,我发现这个问题的答案比我想象的要复杂得多。有三种不同的方案需要解决。
我会一次接受这些中的每一个。第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
}
}
}