Spring SAML-在运行时读取和刷新IdP元数据 [英] Spring SAML - Reading and refreshing IdP metadata at runtime

查看:232
本文介绍了Spring SAML-在运行时读取和刷新IdP元数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将WSO2和SSOCircle与Spring-SAML扩展一起使用.我们目前正在测试配置,并在applicationContext中定义了2个IdP和2个SP.因此,目前,我们在spring xml配置中有2个静态定义的IdP,并且可以正常工作.出于测试目的,我们结合使用CachingMetadataManager和ResourceBackedMetadataProvider,因此IdP元数据内置在我们的WAR存档中.样本:

I am using WSO2 and SSOCircle with the Spring-SAML extension. We are testing configurations at this time and have defined 2 IdP's and 2 SP's within our applicationContext. So, currently, we have 2 statically defined IdP's within our spring xml config and this is working. For testing purpose we are using the combination of CachingMetadataManager and ResourceBackedMetadataProvider so the IdP metadata is built inside of our WAR archive. Sample:

<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构造函数,该构造函数可以动态加载所有Metadata provider,但这是不可能的,因为CachingMetadataManager仅定义一个必须包含MetadataProvider List的构造函数.我最终做了以下事情:

For production, we want to be able to store our IdP metadata in a database (centrally located). I want to be able to add, remove and modify the metadata without redeploying the WAR or restarting the server(s). Initially, I thought I could override the CachingMetadataManager and define a noarg constructor that could load all Metadata providers dynamically but this is not possible because the CachingMetadataManager only defines a single constructor that must take in a MetadataProvider List. I ended up doing the following:

<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元数据列表可以简单定义为:

Bean metadataList can be defined simply as:

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 does allow me to read in the IdP metadata dynamically. My logic falls down when it comes to refreshing though. I found this post on the spring forum, however it is 3 to 4 years old. What is the best way to dynamically read, add and update IdP metadata, cache it and have the cache refresh at some interval? In my case 1 row in a DB table would equate to a single IdP metadata definition.

推荐答案

在阅读了几篇文章并扫描了源代码之后,我发现此问题的答案比我想象的要复杂.确实有3种不同的情况需要解决.

After reading several more posts and scanning source code, I discovered the answer to this question is more complex than I thought. There are really 3 different scenarios to address.

  1. 从数据库表中初步读取所有IdP元数据提供程序
  2. 过期并重新读取IdP元数据XML数据
  3. 动态添加和删除提供程序,而无需更改配置或重新启动服务器

我将一次接受这些.第1项:可能有几种方法可以解决此问题,但请查看上面的DBMetadataProviderList类(在我的OP中),以作为一种快速而肮脏的解决方案.这是更完整的构造函数代码:

I'll take on each of these one at a time. Item 1: There are probably several ways to solve this but review the DBMetadataProviderList class (in my OP) above as a quick and dirty solution. Here is more complete constructor code:

//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内置了缓存刷新功能.以下是重要部分(仅示例代码):

To solve item #2 I used the FilesystemMetadataProvider as a guide and created a DBMetadataProvider class. By extending the AbstractReloadingMetadataProvider class and implementing the fetchMetadata() method we have built-in cache refreshing thanks to opensaml. Below are the important parts (example code only):

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
    }
  }

 }

帖子.

This resource helped me get to the right technique for a cache reloading metadata provider class. Finally, item #3 can be solved by implementing this post.

这篇关于Spring SAML-在运行时读取和刷新IdP元数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆