Spring SAML - 在运行时读取和刷新IdP元数据

时间:2017-03-20 14:59:45

标签: spring metadata spring-saml

我正在使用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元数据定义。

1 个答案:

答案 0 :(得分:1)

在阅读了几篇帖子并扫描源代码之后,我发现这个问题的答案比我想象的要复杂得多。有三种不同的方案需要解决。

  1. 从数据库表中初始读取所有IdP元数据提供程序
  2. 过期并重新读取IdP元数据XML数据
  3. 在没有配置更改或服务器重新启动的情况下动态添加和删除提供程序
  4. 我会一次接受这些中的每一个。第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帮助我找到了一个缓存重新加载元数据提供程序类的正确技术。最后,可以通过实现此post来解决第3项。