[java-identity-provider] branch master updated: JSPT-79 - Review date and time handling for Java 8

Scott Cantor cantor.2 at osu.edu
Thu Mar 14 11:53:47 EDT 2019


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch master
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=73035003021e5fc1db7e6094fbe27020f9c13cf1

The following commit(s) were added to refs/heads/master by this push:
       new  7303500   JSPT-79 - Review date and time handling for Java 8
7303500 is described below

commit 73035003021e5fc1db7e6094fbe27020f9c13cf1
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Mar 14 11:53:44 2019 -0400

    JSPT-79 - Review date and time handling for Java 8
    
    https://issues.shibboleth.net/jira/browse/JSPT-79
    
    Nearly complete remediation of Duration annotation out of IdP.
    
    Also did some removal of deprecated profile config accessors.
---
 .../idp/attribute/impl/JDBCPairwiseIdStore.java    |  34 +++---
 .../attribute/impl/JDBCPairwiseIdStoreTest.java    |   9 +-
 .../impl/AbstractExecutableStatementBuilder.java   |  22 ++--
 .../impl/FormatExecutableStatementBuilder.java     |  35 ++----
 .../dc/rdbms/impl/RDBMSDataConnectorTest.java      |   3 +-
 .../dc/ldap/impl/LDAPDataConnectorParser.java      |   1 +
 .../dc/rdbms/impl/RDBMSDataConnectorParser.java    |  20 +---
 .../idp/authn/AuthenticationFlowDescriptor.java    |  60 ++++++-----
 .../idp/authn/context/AuthenticationContext.java   |  28 ++---
 .../authn/AuthenticationFlowDescriptorTest.java    |  23 ++--
 .../idp/authn/impl/FilterFlowsByForcedAuthn.java   |   2 +-
 .../PopulateMultiFactorAuthenticationContext.java  |   4 +-
 .../impl/TransitionMultiFactorAuthentication.java  |   2 +-
 .../authn/impl/FilterFlowsByForceAuthnTest.java    |   5 +-
 ...pulateMultiFactorAuthenticationContextTest.java |  11 +-
 .../config/impl/AbstractProtocolConfiguration.java |  48 ++++-----
 .../idp/cas/config/impl/LoginConfiguration.java    |   2 -
 .../idp/cas/config/impl/ProxyConfiguration.java    |   2 -
 .../idp/cas/config/impl/ValidateConfiguration.java |   5 +-
 .../idp/cas/flow/impl/GrantProxyTicketAction.java  |   2 +-
 .../cas/flow/impl/GrantServiceTicketAction.java    |   2 +-
 .../cas/flow/impl/ValidateProxyCallbackAction.java |   2 +-
 .../impl/AbstractConsentIndexedStorageAction.java  |   9 +-
 .../storage/impl/CreateGlobalConsentResult.java    |   2 +-
 .../consent/flow/storage/impl/CreateResult.java    |   2 +-
 .../idp/consent/storage/impl/ConsentResult.java    |   6 +-
 .../idp/profile/config/SecurityConfiguration.java  |  24 ++---
 .../AbstractProfileInterceptorResult.java          |  23 ++--
 .../interceptor/ProfileInterceptorResult.java      |   8 +-
 .../idp/profile/logic/DateAttributePredicate.java  |  13 ---
 .../AbstractProfileInterceptorResultTest.java      |  26 ++---
 .../WriteProfileInterceptorResultToStorage.java    |   8 +-
 ...WriteProfileInterceptorResultToStorageTest.java |  12 +--
 .../config/AbstractSAMLProfileConfiguration.java   |  53 ++++-----
 .../DurationConfigurationLookupStrategy.java       |  20 ++--
 .../profile/config/SAMLProfileConfiguration.java   |   8 +-
 .../navigate/AssertionLifetimeLookupFunction.java  |   2 +-
 .../navigate/SessionLifetimeLookupFunction.java    |   6 +-
 .../config/BrowserSSOProfileConfiguration.java     | 118 +++------------------
 .../AbstractSAMLProfileConfigurationTest.java      |  25 ++---
 .../config/BrowserSSOProfileConfigurationTest.java |  60 ++++-------
 .../impl/StoredTransientIdGenerationStrategy.java  |  31 +++---
 .../profile/impl/AddAuthnStatementToAssertion.java |  11 +-
 .../impl/PopulateDelegationContextTest.java        |  20 ----
 .../impl/AddAuthnStatementToAssertionTest.java     |   3 +-
 .../impl/ExtractActiveAuthenticationResults.java   |   5 +-
 .../idp/session/impl/StorageBackedIdPSession.java  |  12 +--
 .../impl/StorageBackedSessionManagerTest.java      |   8 +-
 48 files changed, 339 insertions(+), 498 deletions(-)

diff --git a/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStore.java b/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStore.java
index dd44c7f..cacc4fb 100644
--- a/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStore.java
+++ b/idp-attribute-impl/src/main/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStore.java
@@ -24,6 +24,7 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Timestamp;
 import java.sql.Types;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -37,7 +38,6 @@ import javax.sql.DataSource;
 import net.shibboleth.idp.attribute.DurablePairwiseIdStore;
 import net.shibboleth.idp.attribute.PairwiseId;
 import net.shibboleth.idp.attribute.PairwiseIdStore;
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.Live;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -83,8 +83,8 @@ public class JDBCPairwiseIdStore extends AbstractInitializableComponent implemen
     /** JDBC data source for retrieving connections. */
     @NonnullAfterInit private DataSource dataSource;
 
-    /** Timeout of SQL queries in milliseconds. */
-    @Duration @NonNegative private long queryTimeout;
+    /** Timeout of SQL queries. */
+    @Nonnull private Duration queryTimeout;
 
     /** Number of times to retry a transaction if it rolls back. */
     @NonNegative private int transactionRetry;
@@ -147,7 +147,7 @@ public class JDBCPairwiseIdStore extends AbstractInitializableComponent implemen
     public JDBCPairwiseIdStore() {
         transactionRetry = 3;
         retryableErrors = Arrays.asList("23000", "23505");
-        queryTimeout = 5000;
+        queryTimeout = Duration.ofSeconds(5);
         verifyDatabase = true;
         
         tableName = "shibpid";
@@ -184,21 +184,23 @@ public class JDBCPairwiseIdStore extends AbstractInitializableComponent implemen
     /**
      * Get the SQL query timeout.
      * 
-     * @return the timeout in milliseconds
+     * @return the timeout
      */
-    @NonNegative @Duration public long getQueryTimeout() {
+    @Nonnull public Duration getQueryTimeout() {
         return queryTimeout;
     }
     
     /**
-     * Set the SQL query timeout. Defaults to 5000.
+     * Set the SQL query timeout. Defaults to 5s.
      * 
-     * @param timeout the timeout to set in milliseconds
+     * @param timeout the timeout to set
      */
-    @Duration public void setQueryTimeout(@Duration @NonNegative final long timeout) {
+    public void setQueryTimeout(@Nonnull final Duration timeout) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        Constraint.isNotNull(timeout, "Timeout cannot be null");
+        Constraint.isFalse(timeout.isNegative(), "Timeout cannot be negative");
         
-        queryTimeout = Constraint.isGreaterThanOrEqual(0, timeout, "Timeout must be greater than or equal to 0");
+        queryTimeout = timeout;
     }
 
     /**
@@ -557,7 +559,7 @@ public class JDBCPairwiseIdStore extends AbstractInitializableComponent implemen
         while (true) {
             try (final Connection dbConn = getConnection(false)) {
                 final PreparedStatement statement = dbConn.prepareStatement(getBySourceSelectSQL);
-                statement.setQueryTimeout((int) (queryTimeout / 1000));
+                statement.setQueryTimeout((int) queryTimeout.toSeconds());
                 statement.setString(1, pid.getIssuerEntityID());
                 statement.setString(2, pid.getRecipientEntityID());
                 statement.setString(3, pid.getSourceSystemId());
@@ -638,7 +640,7 @@ public class JDBCPairwiseIdStore extends AbstractInitializableComponent implemen
     
         try (final Connection dbConn = getConnection(true)) {
             final PreparedStatement statement = dbConn.prepareStatement(getByIssuedSelectSQL);
-            statement.setQueryTimeout((int) (queryTimeout / 1000));
+            statement.setQueryTimeout((int) queryTimeout.toSeconds());
     
             statement.setString(1, pid.getIssuerEntityID());
             statement.setString(2, pid.getRecipientEntityID());
@@ -685,7 +687,7 @@ public class JDBCPairwiseIdStore extends AbstractInitializableComponent implemen
         
         try (final Connection dbConn = getConnection(true)) {
             final PreparedStatement statement = dbConn.prepareStatement(deactivateSQL);
-            statement.setQueryTimeout((int) (queryTimeout / 1000));
+            statement.setQueryTimeout((int) queryTimeout.toSeconds());
             statement.setTimestamp(1, deactivationTime);
             statement.setString(2, pid.getIssuerEntityID());
             statement.setString(3, pid.getRecipientEntityID());
@@ -719,7 +721,7 @@ public class JDBCPairwiseIdStore extends AbstractInitializableComponent implemen
         
         try (final Connection dbConn = getConnection(true)) {
             final PreparedStatement statement = dbConn.prepareStatement(attachSQL);
-            statement.setQueryTimeout((int) (queryTimeout / 1000));
+            statement.setQueryTimeout((int) queryTimeout.toSeconds());
             statement.setString(1, pid.getPeerProvidedId());
             statement.setString(2, pid.getIssuerEntityID());
             statement.setString(3, pid.getRecipientEntityID());
@@ -767,7 +769,7 @@ public class JDBCPairwiseIdStore extends AbstractInitializableComponent implemen
         log.trace("Setting prepared statement parameter {}: {}", 8, entry.getDeactivationTime());
         
         final PreparedStatement statement = dbConn.prepareStatement(insertSQL);
-        statement.setQueryTimeout((int) (queryTimeout / 1000));
+        statement.setQueryTimeout((int) queryTimeout.toSeconds());
     
         statement.setString(1, entry.getIssuerEntityID());
         statement.setString(2, entry.getRecipientEntityID());
@@ -840,7 +842,7 @@ public class JDBCPairwiseIdStore extends AbstractInitializableComponent implemen
 
         try (final Connection conn = getConnection(true)) {
             final PreparedStatement statement = conn.prepareStatement(deleteSQL);
-            statement.setQueryTimeout((int) (queryTimeout / 1000));
+            statement.setQueryTimeout((int) queryTimeout.toSeconds());
             statement.setString(1, "http://dummy.com/idp/" + uuid);
             statement.executeUpdate();
         }
diff --git a/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStoreTest.java b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStoreTest.java
index a6b5ac8..8af5473 100644
--- a/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStoreTest.java
+++ b/idp-attribute-impl/src/test/java/net/shibboleth/idp/attribute/impl/JDBCPairwiseIdStoreTest.java
@@ -20,6 +20,7 @@ package net.shibboleth.idp.attribute.impl;
 import java.io.IOException;
 import java.sql.Connection;
 import java.sql.SQLException;
+import java.time.Duration;
 import java.util.Objects;
 import java.util.UUID;
 
@@ -67,8 +68,8 @@ public class JDBCPairwiseIdStoreTest {
         store.setDataSource(testSource);
         
         Assert.assertEquals(store.getDataSource(), testSource);
-        Assert.assertEquals(store.getQueryTimeout(), 5000);
-        store.setQueryTimeout(1);
+        Assert.assertEquals(store.getQueryTimeout(), Duration.ofSeconds(5));
+        store.setQueryTimeout(Duration.ofMillis(1));
         
         try {
             store.getBySourceValue(new PairwiseId(), true);
@@ -86,13 +87,13 @@ public class JDBCPairwiseIdStoreTest {
         }
         store.initialize();
         try {
-            store.setQueryTimeout(0);
+            store.setQueryTimeout(Duration.ZERO);
             Assert.fail("work after initialize");
         } catch (final UnmodifiableComponentException e) {
             // OK
         }
         Assert.assertEquals(store.getDataSource(), testSource);
-        Assert.assertEquals(store.getQueryTimeout(), 1);
+        Assert.assertEquals(store.getQueryTimeout(), Duration.ofMillis(1));
     }
     
     private boolean comparePersistentIdEntrys(@Nonnull PairwiseId one, @Nonnull PairwiseId other)
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/AbstractExecutableStatementBuilder.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/AbstractExecutableStatementBuilder.java
index 260b9ba..579aeb8 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/AbstractExecutableStatementBuilder.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/AbstractExecutableStatementBuilder.java
@@ -21,6 +21,7 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.time.Duration;
 import java.util.List;
 import java.util.Map;
 
@@ -33,6 +34,7 @@ import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
 import net.shibboleth.idp.attribute.resolver.dc.impl.ExecutableSearchBuilder;
 import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
  * Basis of statement builder. The derived classes just have to provide the per request sql string.
@@ -41,24 +43,32 @@ public abstract class AbstractExecutableStatementBuilder extends AbstractInitial
         ExecutableSearchBuilder<ExecutableStatement> {
 
     /** Query timeout. */
-    private int queryTimeout = 5000;
+    @Nonnull private Duration queryTimeout;
 
+    /** Constructor. */
+    public AbstractExecutableStatementBuilder() {
+        queryTimeout = Duration.ofSeconds(5);
+    }
+    
     /**
      * Gets the timeout of the SQL query.
      * 
-     * @return timeout of the SQL query in seconds
+     * @return timeout of the SQL query
      */
-    public int getQueryTimeout() {
+    @Nonnull public Duration getQueryTimeout() {
         return queryTimeout;
     }
 
     /**
      * Sets the timeout of the SQL query.
      * 
-     * @param timeout of the SQL query in seconds
+     * @param timeout of the SQL query
      */
-    public void setQueryTimeout(final int timeout) {
+    public void setQueryTimeout(@Nonnull final Duration timeout) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        Constraint.isNotNull(timeout, "Query timeout cannot be null");
+        Constraint.isFalse(timeout.isNegative(), "Query timeout cannot be negative");
+        
         queryTimeout = timeout;
     }
 
@@ -89,7 +99,7 @@ public abstract class AbstractExecutableStatementBuilder extends AbstractInitial
             /** {@inheritDoc} */
             @Nonnull public ResultSet execute(@Nonnull final Connection connection) throws SQLException {
                 final Statement stmt = connection.createStatement();
-                stmt.setQueryTimeout(queryTimeout);
+                stmt.setQueryTimeout((int) queryTimeout.toSeconds());
                 return stmt.executeQuery(query);
             }
 
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/FormatExecutableStatementBuilder.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/FormatExecutableStatementBuilder.java
index 55945f1..1a38581 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/FormatExecutableStatementBuilder.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/FormatExecutableStatementBuilder.java
@@ -25,6 +25,7 @@ import javax.annotation.Nonnull;
 
 import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
@@ -36,40 +37,24 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
 public class FormatExecutableStatementBuilder extends AbstractExecutableStatementBuilder {
 
     /** SQL query string. */
-    private String sqlQuery;
-
-    /**
-     * Constructor.
+    @NonnullAfterInit private String sqlQuery;
+    
+    /** Set the query to search the database. 
+     * @param query query to search the database
      */
-    public FormatExecutableStatementBuilder() {
+    public void setQuery(@Nonnull final String query) {
+        sqlQuery = Constraint.isNotNull(query, "SQL query cannot be null");        
     }
 
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
         if (null == sqlQuery) {
             throw new  ComponentInitializationException(
-                    "FormatExecutableStatementBuilder: SQL query can not be null");
-        }
-        if (getQueryTimeout() <= 0) {
-            throw new ComponentInitializationException(
-                    "FormatExecutableStatementBuilder: Query timeout must be greater than zero");
+                    "FormatExecutableStatementBuilder: SQL query cannot be null");
         }
-        super.doInitialize();
-    }
-    
-    /** Set the query to search the database. 
-     * @param query query to search the database
-     */
-    public void setQuery(@Nonnull final String query) {
-        sqlQuery = Constraint.isNotNull(query, "SQL query can not be null");        
-    }
-    
-    /** Set the search timeout.
-     * @param timeout search timeout
-     */
-    public void setTimeOut(@Nonnull final int timeout) {
-        setQueryTimeout((int) Constraint.isGreaterThanOrEqual(0, timeout, "Query timeout must be greater than zero"));
     }
 
     /** {@inheritDoc} */
diff --git a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/RDBMSDataConnectorTest.java b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/RDBMSDataConnectorTest.java
index ce8a1a6..32935a4 100644
--- a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/RDBMSDataConnectorTest.java
+++ b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/RDBMSDataConnectorTest.java
@@ -18,6 +18,7 @@
 package net.shibboleth.idp.attribute.resolver.dc.rdbms.impl;
 
 import java.sql.SQLException;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -393,7 +394,7 @@ public class RDBMSDataConnectorTest {
       return builder;
     }
 
-    static protected FormatExecutableStatementBuilder newFormatExecutableStatementBuilder(@Nonnull final String query, @Nonnull final int timeout) {
+    static protected FormatExecutableStatementBuilder newFormatExecutableStatementBuilder(@Nonnull final String query, @Nonnull final Duration timeout) {
         final FormatExecutableStatementBuilder builder = new FormatExecutableStatementBuilder();
         builder.setQuery(query);
         builder.setQueryTimeout(timeout);
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
index 27fae5b..a5d2dee 100644
--- a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
@@ -640,6 +640,7 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                 final BeanDefinitionBuilder period =
                         BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildDuration");
                 period.addConstructorArgValue(validateTimerPeriod);
+                // Convert to seconds.
                 period.addConstructorArgValue(1000);
                 poolConfig.addPropertyValue("validatePeriod", period.getBeanDefinition());
             } else {
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/rdbms/impl/RDBMSDataConnectorParser.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/rdbms/impl/RDBMSDataConnectorParser.java
index 5bd9ff9..bbc5fa3 100644
--- a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/rdbms/impl/RDBMSDataConnectorParser.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/rdbms/impl/RDBMSDataConnectorParser.java
@@ -39,7 +39,6 @@ import net.shibboleth.idp.attribute.resolver.spring.dc.AbstractDataConnectorPars
 import net.shibboleth.idp.attribute.resolver.spring.dc.impl.CacheConfigParser;
 import net.shibboleth.idp.attribute.resolver.spring.dc.impl.ManagedConnectionParser;
 import net.shibboleth.idp.attribute.resolver.spring.impl.AttributeResolverNamespaceHandler;
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
@@ -203,11 +202,7 @@ public class RDBMSDataConnectorParser extends AbstractDataConnectorParser {
 
             final String queryTimeout = AttributeSupport.getAttributeValue(configElement, new QName("queryTimeout"));
             if (queryTimeout != null) {
-                final BeanDefinitionBuilder duration =
-                        BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildDuration");
-                duration.addConstructorArgValue(queryTimeout);
-                duration.addConstructorArgValue(1);
-                templateBuilder.addPropertyValue("queryTimeout", duration.getBeanDefinition());
+                templateBuilder.addPropertyValue("queryTimeout", queryTimeout);
             }
 
             final List<Element> queryTemplates = ElementSupport.getChildElementsByTagNameNS(configElement, 
@@ -331,19 +326,6 @@ public class RDBMSDataConnectorParser extends AbstractDataConnectorParser {
         @Nonnull @NotEmpty private String getLogPrefix() {
             return logPrefix;
         }
-
-        /**
-         * Converts the supplied duration to milliseconds and divides it by the divisor. Useful for modifying durations
-         * while resolving property replacement.
-         * 
-         * @param duration the duration (which may have gone through spring translation from iso to long)
-         * @param divisor to modify the duration with
-         * 
-         * @return result of the division
-         */
-        @Duration public static long buildDuration(@Duration final long duration, final long divisor) {
-            return duration / divisor;
-        } 
     }
     
 }
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
index c861c61..c06cd87 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
@@ -19,6 +19,8 @@ package net.shibboleth.idp.authn;
 
 import java.io.IOException;
 import java.security.Principal;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -33,11 +35,8 @@ import javax.security.auth.Subject;
 
 import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
 import net.shibboleth.idp.profile.FlowDescriptor;
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
 import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -68,7 +67,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     @Nonnull @NotEmpty public static final String FLOW_ID_PREFIX = "authn/";
 
     /** Additional allowance for storage of result records to avoid race conditions during use. */
-    public static final long STORAGE_EXPIRATION_OFFSET;
+    @Nonnull public static final Duration STORAGE_EXPIRATION_OFFSET;
 
     /** Whether this flow supports non-browser clients. */
     private boolean supportsNonBrowser;
@@ -82,11 +81,11 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     /** Whether this flow allows reuse of its results. */
     @Nonnull private Predicate<ProfileRequestContext> reuseCondition;
 
-    /** Maximum amount of time in milliseconds, since first usage, a flow should be considered active. */
-    @Duration @NonNegative private long lifetime;
+    /** Maximum amount of time since first usage that a flow should be considered active. */
+    @Nullable private Duration lifetime;
 
-    /** Maximum amount of time in milliseconds, since last usage, a flow should be considered active. */
-    @Duration @Positive private long inactivityTimeout;
+    /** Maximum amount of time since last usage that a flow should be considered active. */
+    @Nonnull private Duration inactivityTimeout;
 
     /**
      * Supported principals, indexed by type, that the flow can produce. Implemented for the moment using the Subject
@@ -109,7 +108,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
         reuseCondition = Predicates.alwaysTrue();
         supportedPrincipals = new Subject();
         activationCondition = Predicates.alwaysTrue();
-        inactivityTimeout = 30 * 60 * 1000;
+        inactivityTimeout = Duration.ofMinutes(30);
         principalWeightMap = Collections.emptyMap();
     }
     
@@ -200,29 +199,31 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     }
 
     /**
-     * Get the maximum amount of time in milliseconds, since first usage, a flow should be considered active. A value
-     * of 0 indicates that there is no upper limit on the lifetime on an active flow.
+     * Get the maximum amount of time, since first usage, a flow should be considered active. A null
+     * indicates that there is no upper limit on the lifetime on an active flow.
      * 
-     * @return maximum amount of time in milliseconds a flow should be considered active, never less than 0
+     * @return maximum amount of time a flow should be considered active
      */
-    @NonNegative @Duration public long getLifetime() {
+    @Nullable public Duration getLifetime() {
         return lifetime;
     }
 
     /**
-     * Set the maximum amount of time in milliseconds, since first usage, a flow should be considered active. A value
-     * of 0 indicates that there is no upper limit on the lifetime on an active flow.
+     * Set the maximum amount of time, since first usage, a flow should be considered active. A null value
+     * indicates that there is no upper limit on the lifetime on an active flow.
      * 
-     * @param flowLifetime the lifetime for the flow, must be 0 or greater
+     * @param flowLifetime the lifetime for the flow
      */
-    @Duration public void setLifetime(@Duration @NonNegative final long flowLifetime) {
+    public void setLifetime(@Nullable final Duration flowLifetime) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        Constraint.isFalse(flowLifetime != null && (flowLifetime.isNegative() || flowLifetime.isZero()),
+                "Lifetime must be null or greater than 0");
 
-        lifetime = Constraint.isGreaterThanOrEqual(0, flowLifetime, "Lifetime must be greater than or equal to 0");
+        lifetime = flowLifetime;
     }
 
     /**
-     * Get the maximum amount of time in milliseconds, since the last usage, a flow should be considered active.
+     * Get the maximum amount of time, since the last usage, a flow should be considered active.
      * 
      * <p>
      * Defaults to 30 minutes.
@@ -230,19 +231,21 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
      * 
      * @return the duration
      */
-    @Duration @Positive public long getInactivityTimeout() {
+    @Nonnull public Duration getInactivityTimeout() {
         return inactivityTimeout;
     }
 
     /**
-     * Set the maximum amount of time in milliseconds, since the last usage, a flow should be considered active.
+     * Set the maximum amount of time, since the last usage, a flow should be considered active.
      * 
      * @param timeout the flow inactivity timeout, must be greater than zero
      */
-    @Duration public void setInactivityTimeout(@Duration @Positive final long timeout) {
+    public void setInactivityTimeout(@Nonnull final Duration timeout) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        Constraint.isNotNull(timeout, "Inactivity timeout cannot be null");
+        Constraint.isFalse(timeout.isNegative() || timeout.isZero(), "Inactivity timeout must be greater than 0");
 
-        inactivityTimeout = Constraint.isGreaterThan(0, timeout, "Inactivity timeout must be greater than 0");
+        inactivityTimeout = timeout;
     }
 
     /**
@@ -257,11 +260,10 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
         Constraint.isTrue(result.getAuthenticationFlowId().equals(getId()),
                 "AuthenticationResult was not produced by this flow");
 
-        final long now = System.currentTimeMillis();
-        if (getLifetime() > 0 && result.getAuthenticationInstant().plusMillis(getLifetime()).toEpochMilli() <= now) {
+        final Instant now = Instant.now();
+        if (getLifetime() != null && now.isAfter(result.getAuthenticationInstant().plus(getLifetime()))) {
             return false;
-        } else if (getInactivityTimeout() > 0
-                && result.getLastActivityInstant().plusMillis(getInactivityTimeout()).toEpochMilli() <= now) {
+        } else if (now.isAfter(result.getLastActivityInstant().plus(getInactivityTimeout()))) {
             return false;
         }
 
@@ -370,7 +372,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
 
         // Back the expiration off by the inactivity timeout to recover the last activity time.
         return resultSerializer.deserialize(version, context, key, value, (expiration != null) ? expiration
-                - inactivityTimeout - STORAGE_EXPIRATION_OFFSET : null);
+                - inactivityTimeout.toMillis() - STORAGE_EXPIRATION_OFFSET.toMillis() : null);
     }
 
     /**
@@ -448,6 +450,6 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     }
     
     static {
-        STORAGE_EXPIRATION_OFFSET = 10 * 60 * 1000;
+        STORAGE_EXPIRATION_OFFSET = Duration.ofMinutes(10);
     }
 }
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
index db9378b..7bc299d 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
@@ -19,6 +19,7 @@ package net.shibboleth.idp.authn.context;
 
 import java.lang.reflect.Constructor;
 import java.security.Principal;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -36,9 +37,7 @@ import net.shibboleth.idp.authn.AuthenticationResult;
 import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
 import net.shibboleth.idp.authn.principal.PrincipalEvalPredicateFactoryRegistry;
 import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.Live;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -78,8 +77,8 @@ public final class AuthenticationContext extends BaseContext {
     /** A non-normative hint some protocols support to indicate who the subject might be. */
     @Nullable private String hintedName;
     
-    /** Allowed time in ms since an {@link AuthenticationResult} was created that it can be reused for this request. */
-    @NonNegative @Duration private long maxAge;
+    /** Allowed time since an {@link AuthenticationResult} was created that it can be reused for this request. */
+    @Nullable private Duration maxAge;
 
     /** Lookup strategy for a fixed event to return from validators for testing. */
     @Nullable private Function<ProfileRequestContext,String> fixedEventLookupStrategy;
@@ -330,33 +329,36 @@ public final class AuthenticationContext extends BaseContext {
     }
     
     /**
-     * Get duration in milliseconds since an {@link AuthenticationResult} was created that
+     * Get duration since an {@link AuthenticationResult} was created that
      * allows it to be reused for this request.
      * 
-     * <p>If zero, no constraint is applied.</p>
+     * <p>If null, no constraint is applied.</p>
      * 
-     * @return duration in milliseconds, or zero
+     * @return duration
      * 
      * @since 3.4.0
      */
-    @NonNegative @Duration public long getMaxAge() {
+    @Nullable public Duration getMaxAge() {
         return maxAge;
     }
     
     /**
-     * Set duration in milliseconds since an {@link AuthenticationResult} was created that
+     * Set duration since an {@link AuthenticationResult} was created that
      * allows it to be reused for this request.
      * 
-     * <p>Set to zero to apply no constraint.</p>
+     * <p>Set to null to apply no constraint.</p>
      * 
-     * @param age duration in milliseconds, or zero
+     * @param age duration
      * 
      * @return this context
      * 
      * @since 3.4.0
      */
-    @Nonnull public AuthenticationContext setMaxAge(@NonNegative @Duration final long age) {
-        maxAge = Constraint.isGreaterThanOrEqual(0, age, "MaxAge cannot be negative");
+    @Nonnull public AuthenticationContext setMaxAge(@Nullable final Duration age) {
+        
+        Constraint.isFalse(age != null && (age.isNegative() || age.isZero()), "MaxAge must be null or greater than 0");
+        
+        maxAge = age;
         return this;
     }
     
diff --git a/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptorTest.java b/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptorTest.java
index 5e66ea7..0216314 100644
--- a/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptorTest.java
+++ b/idp-authn-api/src/test/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptorTest.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.authn;
 
+import java.time.Duration;
 import java.util.Arrays;
 
 import net.shibboleth.idp.authn.principal.UsernamePrincipal;
@@ -46,27 +47,27 @@ public class AuthenticationFlowDescriptorTest {
 
     /** Tests mutating lifetime. */
     @Test public void testLifetime() {
-        descriptor.setLifetime(10);
-        Assert.assertEquals(descriptor.getLifetime(), 10);
+        descriptor.setLifetime(Duration.ofMillis(10));
+        Assert.assertEquals(descriptor.getLifetime(), Duration.ofMillis(10));
 
         try {
-            descriptor.setLifetime(-10);
+            descriptor.setLifetime(Duration.ofMillis(-10));
             Assert.fail();
         } catch (ConstraintViolationException e) {
-            Assert.assertEquals(descriptor.getLifetime(), 10);
+            Assert.assertEquals(descriptor.getLifetime(), Duration.ofMillis(10));
         }
     }
 
     /** Tests mutating inactivity timeout. */
     @Test public void testInactivityTimeout() {
-        descriptor.setInactivityTimeout(10);
-        Assert.assertEquals(descriptor.getInactivityTimeout(), 10);
+        descriptor.setInactivityTimeout(Duration.ofMillis(10));
+        Assert.assertEquals(descriptor.getInactivityTimeout(), Duration.ofMillis(10));
 
         try {
-            descriptor.setInactivityTimeout(-10);
+            descriptor.setInactivityTimeout(Duration.ofMillis(-10));
             Assert.fail();
         } catch (ConstraintViolationException e) {
-            Assert.assertEquals(descriptor.getInactivityTimeout(), 10);
+            Assert.assertEquals(descriptor.getInactivityTimeout(), Duration.ofMillis(10));
         }
     }
     
@@ -126,15 +127,15 @@ public class AuthenticationFlowDescriptorTest {
         
         Thread.sleep(20);
         
-        descriptor.setLifetime(10);
+        descriptor.setLifetime(Duration.ofMillis(10));
         Assert.assertFalse(descriptor.isResultActive(result));
         
-        descriptor.setLifetime(5000);
+        descriptor.setLifetime(Duration.ofSeconds(5));
         Assert.assertTrue(descriptor.isResultActive(result));
         
         Thread.sleep(20);
         
-        descriptor.setInactivityTimeout(10);
+        descriptor.setInactivityTimeout(Duration.ofMillis(10));
         Assert.assertFalse(descriptor.isResultActive(result));
         
         result.setLastActivityInstantToNow();
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByForcedAuthn.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByForcedAuthn.java
index 40609ad..79cfe4d 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByForcedAuthn.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByForcedAuthn.java
@@ -49,7 +49,7 @@ public class FilterFlowsByForcedAuthn extends AbstractAuthenticationAction {
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
         
-        if (!authenticationContext.isForceAuthn() && authenticationContext.getMaxAge() == 0) {
+        if (!authenticationContext.isForceAuthn() && authenticationContext.getMaxAge() == null) {
             log.debug("{} Request does not have forced authentication requirement, nothing to do", getLogPrefix());
             return false;
         }
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java
index abaae08..f549e5c 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java
@@ -231,8 +231,8 @@ public class PopulateMultiFactorAuthenticationContext extends AbstractAuthentica
             if (descriptor != null) {
                 if (descriptor.test(profileRequestContext)) {
                     if (descriptor.isResultActive(candidate)) {
-                        if (authenticationContext.getMaxAge() > 0
-                                && candidate.getAuthenticationInstant().plusMillis(
+                        if (authenticationContext.getMaxAge() != null
+                                && candidate.getAuthenticationInstant().plus(
                                         authenticationContext.getMaxAge()).isBefore(Instant.now())) {
                             log.debug("{} Ignoring active result from login flow {} due to maxAge on request",
                                     getLogPrefix(), candidate.getAuthenticationFlowId());
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
index 9e045db..8916ba7 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
@@ -284,7 +284,7 @@ public class TransitionMultiFactorAuthentication extends AbstractAuthenticationA
                         getLogPrefix(), flowId);
                 ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_PASSIVE);
                 return;
-            } else if ((authenticationContext.isForceAuthn() || authenticationContext.getMaxAge() > 0)
+            } else if ((authenticationContext.isForceAuthn() || authenticationContext.getMaxAge() != null)
                     && !flow.isForcedAuthenticationSupported()) {
                 log.error("{} Targeted login flow '{}' does not support forced re-authentication",
                         getLogPrefix(), flowId);
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByForceAuthnTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByForceAuthnTest.java
index 4c09972..042b561 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByForceAuthnTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByForceAuthnTest.java
@@ -21,6 +21,8 @@ import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.profile.ActionTestingSupport;
 
+import java.time.Duration;
+
 import org.springframework.webflow.execution.Event;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
@@ -41,7 +43,6 @@ public class FilterFlowsByForceAuthnTest extends BaseAuthenticationContextTest {
     @Test public void testNonForced() throws Exception {
         final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
         authCtx.setForceAuthn(false);
-        authCtx.setMaxAge(0);
         
         final Event event = action.execute(src);
         ActionTestingSupport.assertProceedEvent(event);
@@ -62,7 +63,7 @@ public class FilterFlowsByForceAuthnTest extends BaseAuthenticationContextTest {
 
     @Test public void testPartialFiltering() {
         final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        authCtx.setMaxAge(3600 * 1000);
+        authCtx.setMaxAge(Duration.ofHours(1));
         authCtx.getPotentialFlows().get("test2").setForcedAuthenticationSupported(true);
         
         final Event event = action.execute(src);
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java
index 27ee20d..0c0162c 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.authn.impl;
 
+import java.time.Duration;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.Map;
@@ -109,32 +110,32 @@ public class PopulateMultiFactorAuthenticationContextTest {
         AuthenticationFlowDescriptor desc = new AuthenticationFlowDescriptor();
         desc.setId("foo");
         desc.setResultSerializer(new DefaultAuthenticationResultSerializer());
-        desc.setLifetime(3600 * 1000);
+        desc.setLifetime(Duration.ofHours(1));
         desc.initialize();
         ac.getAvailableFlows().put(desc.getId(), desc);
 
         desc = new AuthenticationFlowDescriptor();
         desc.setId("bar");
         desc.setResultSerializer(new DefaultAuthenticationResultSerializer());
-        desc.setLifetime(3600 * 1000);
+        desc.setLifetime(Duration.ofHours(1));
         desc.initialize();
         ac.getAvailableFlows().put(desc.getId(), desc);
 
         desc = new AuthenticationFlowDescriptor();
         desc.setId("bav");
         desc.setResultSerializer(new DefaultAuthenticationResultSerializer());
-        desc.setLifetime(3600 * 1000);
+        desc.setLifetime(Duration.ofHours(1));
         desc.initialize();
         ac.getAvailableFlows().put(desc.getId(), desc);
 
         desc = new AuthenticationFlowDescriptor();
         desc.setId("bag");
         desc.setResultSerializer(new DefaultAuthenticationResultSerializer());
-        desc.setLifetime(3600 * 1000);
+        desc.setLifetime(Duration.ofHours(1));
         desc.initialize();
         ac.getAvailableFlows().put(desc.getId(), desc);
 
-        ac.setMaxAge(1800 * 1000);
+        ac.setMaxAge(Duration.ofMinutes(30));
         
         action.setTransitionMapLookupStrategy(
                 FunctionSupport.<ProfileRequestContext,Map<String,MultiFactorAuthenticationTransition>>constant(
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/AbstractProtocolConfiguration.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/AbstractProtocolConfiguration.java
index 02a84eb..57bb661 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/AbstractProtocolConfiguration.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/AbstractProtocolConfiguration.java
@@ -17,7 +17,7 @@
 
 package net.shibboleth.idp.cas.config.impl;
 
-import java.util.concurrent.TimeUnit;
+import java.time.Duration;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -27,9 +27,7 @@ import javax.annotation.Nullable;
 import net.shibboleth.idp.cas.ticket.impl.TicketIdentifierGenerationStrategy;
 import net.shibboleth.idp.profile.config.AbstractConditionalProfileConfiguration;
 import net.shibboleth.idp.profile.config.SecurityConfiguration;
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.InitializableComponent;
 import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -50,34 +48,26 @@ public abstract class AbstractProtocolConfiguration extends AbstractConditionalP
     public static final String PROTOCOL_URI = "https://www.apereo.org/cas/protocol";
 
     /** Lookup function to supply {@link #ticketValidityPeriod} property. */
-    @Nullable private Function<ProfileRequestContext, Long> ticketValidityPeriodLookupStrategy;
+    @Nullable private Function<ProfileRequestContext,Duration> ticketValidityPeriodLookupStrategy;
 
     /** Validity time period of tickets. */
-    @Duration @Positive private long ticketValidityPeriod;
+    @Nonnull private Duration ticketValidityPeriod;
 
     /** Whether attributes should be resolved in the course of the profile. */
     @Nonnull private Predicate<ProfileRequestContext> resolveAttributesPredicate;
 
     /**
      * Creates a new configuration instance.
-     * 
-     * @param profileId Unique profile identifier.
-     */
-    public AbstractProtocolConfiguration(@Nonnull @NotEmpty final String profileId) {
-        this(profileId, 15000L);
-    }
-
-    /**
-     * Creates a new configuration instance.
      *
-     * @param profileId Unique profile identifier.
-     * @param ticketTTL Ticket validity period in milliseconds.
+     * @param profileId Unique profile identifier
      */
-    public AbstractProtocolConfiguration(@Nonnull @NotEmpty final String profileId, @Positive final long ticketTTL) {
+    public AbstractProtocolConfiguration(@Nonnull @NotEmpty final String profileId) {
         super(profileId);
+        
         resolveAttributesPredicate = Predicates.alwaysTrue();
-        ticketValidityPeriod = ticketTTL;
-        setSecurityConfiguration(new SecurityConfiguration(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES),
+        ticketValidityPeriod = Duration.ofSeconds(15);
+        
+        setSecurityConfiguration(new SecurityConfiguration(Duration.ofMinutes(5),
                 new TicketIdentifierGenerationStrategy(getDefaultTicketPrefix(), getDefaultTicketLength())));
     }
 
@@ -91,21 +81,22 @@ public abstract class AbstractProtocolConfiguration extends AbstractConditionalP
     /**
      * Get ticket validity period.
      * 
-     * @return Ticket validity period in milliseconds.
+     * @return ticket validity period
      */
-    @Positive @Duration public long getTicketValidityPeriod() {
-        return Constraint.isGreaterThan(0,
-                getIndirectProperty(ticketValidityPeriodLookupStrategy, ticketValidityPeriod),
-                "Ticket validity period must be positive.");
+    @Nonnull public Duration getTicketValidityPeriod() {
+        return getIndirectProperty(ticketValidityPeriodLookupStrategy, ticketValidityPeriod);
     }
 
     /**
      * Sets the ticket validity period.
      * 
-     * @param millis Ticket validity period in milliseconds.
+     * @param ticketTTL ticket validity period
      */
-    @Duration public void setTicketValidityPeriod(@Duration @Positive final long millis) {
-        ticketValidityPeriod = Constraint.isGreaterThan(0, millis, "Ticket validity period must be positive.");
+    public void setTicketValidityPeriod(@Nonnull final Duration ticketTTL) {
+        Constraint.isNotNull(ticketTTL, "Ticket lifetime cannot be null");
+        Constraint.isFalse(ticketTTL.isNegative() || ticketTTL.isZero(), "Ticket lifetime must be greater than 0");
+
+        ticketValidityPeriod = ticketTTL;
     }
 
     /**
@@ -115,7 +106,8 @@ public abstract class AbstractProtocolConfiguration extends AbstractConditionalP
      * 
      * @since 3.3.0
      */
-    public void setTicketValidityPeriodLookupStrategy(@Nullable final Function<ProfileRequestContext, Long> strategy) {
+    public void setTicketValidityPeriodLookupStrategy(
+            @Nullable final Function<ProfileRequestContext,Duration> strategy) {
         ticketValidityPeriodLookupStrategy = strategy;
     }
 
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/LoginConfiguration.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/LoginConfiguration.java
index e60b5dc..d766766 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/LoginConfiguration.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/LoginConfiguration.java
@@ -94,8 +94,6 @@ public class LoginConfiguration extends AbstractProtocolConfiguration
     /** Creates a new instance. */
     public LoginConfiguration() {
         super(PROFILE_ID);
-        // Service tickets valid for 15s by default
-        setTicketValidityPeriod(15000);
         authenticationFlows = Collections.emptySet();
         postAuthenticationFlows = Collections.emptyList();
         defaultAuthenticationContexts = Collections.emptyList();
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/ProxyConfiguration.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/ProxyConfiguration.java
index f2d92f9..01be4c6 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/ProxyConfiguration.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/ProxyConfiguration.java
@@ -41,8 +41,6 @@ public class ProxyConfiguration extends AbstractProtocolConfiguration {
     /** Creates a new instance. */
     public ProxyConfiguration() {
         super(PROFILE_ID);
-        // Proxy tickets valid for 15s by default
-        setTicketValidityPeriod(15000);
     }
 
     /** {@inheritDoc} */
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/ValidateConfiguration.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/ValidateConfiguration.java
index d345afc..73531d8 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/ValidateConfiguration.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/config/impl/ValidateConfiguration.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.cas.config.impl;
 
+import java.time.Duration;
 import java.util.Comparator;
 import java.util.function.Function;
 
@@ -72,9 +73,11 @@ public class ValidateConfiguration extends AbstractProtocolConfiguration {
 
     /** Creates a new instance. */
     public ValidateConfiguration() {
+        super(PROFILE_ID);
+        
         // Ticket validity period for this configuration container applies to proxy-granting tickets
         // Default to 12H
-        super(PROFILE_ID, 12 * 60 * 60 * 1000);
+        setTicketValidityPeriod(Duration.ofHours(12));
         
         pgtIOUGenerator = new TicketIdentifierGenerationStrategy("PGTIOU", 50);
         serviceComparator = new DefaultServiceComparator();
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantProxyTicketAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantProxyTicketAction.java
index eef41d3..30bd139 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantProxyTicketAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantProxyTicketAction.java
@@ -151,7 +151,7 @@ public class GrantProxyTicketAction extends AbstractCASProtocolAction<ProxyTicke
             log.debug("Granting proxy ticket for {}", request.getTargetService());
             pt = casTicketService.createProxyTicket(
                     config.getSecurityConfiguration().getIdGenerator().generateIdentifier(),
-                    Instant.now().plusMillis(config.getTicketValidityPeriod()),
+                    Instant.now().plus(config.getTicketValidityPeriod()),
                     pgt,
                     request.getTargetService());
         } catch (final RuntimeException e) {
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantServiceTicketAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantServiceTicketAction.java
index 81a0d38..901a30e 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantServiceTicketAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/GrantServiceTicketAction.java
@@ -126,7 +126,7 @@ public class GrantServiceTicketAction extends AbstractCASProtocolAction<ServiceT
                     authnResult.getAuthenticationFlowId());
             ticket = ticketServiceEx.createServiceTicket(
                     config.getSecurityConfiguration().getIdGenerator().generateIdentifier(),
-                    Instant.now().plusMillis(config.getTicketValidityPeriod()),
+                    Instant.now().plus(config.getTicketValidityPeriod()),
                     request.getService(),
                     state,
                     request.isRenew());
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java
index c5e7355..f45e5c0 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java
@@ -124,7 +124,7 @@ public class ValidateProxyCallbackAction
         try {
             log.debug("Attempting proxy authentication to {}", proxyCallbackUri);
             proxyValidator.validate(profileRequestContext, proxyCallbackUri);
-            final Instant expiration = Instant.now().plusMillis(config.getTicketValidityPeriod());
+            final Instant expiration = Instant.now().plus(config.getTicketValidityPeriod());
             if (ticket instanceof ServiceTicket) {
                 ticketServiceEx.createProxyGrantingTicket(proxyIds.getPgtId(), expiration, (ServiceTicket) ticket);
             } else {
diff --git a/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/AbstractConsentIndexedStorageAction.java b/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/AbstractConsentIndexedStorageAction.java
index c786b51..b65c2e7 100644
--- a/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/AbstractConsentIndexedStorageAction.java
+++ b/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/AbstractConsentIndexedStorageAction.java
@@ -18,6 +18,7 @@
 package net.shibboleth.idp.consent.flow.storage.impl;
 
 import java.io.IOException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -323,16 +324,18 @@ public class AbstractConsentIndexedStorageAction extends AbstractConsentStorageA
         final String context = result.getStorageContext();
         final String key = result.getStorageKey();
         final String value = result.getStorageValue();
-        final Long expiration = result.getStorageExpiration();
+        final Instant expiration = result.getStorageExpiration();
 
         // Create / update loop until we succeed or exhaust attempts.
         int attempts = 10;
         boolean success = false;
         do {
-            success = getStorageService().create(context, key, value, expiration);
+            success = getStorageService().create(context, key, value,
+                    expiration != null ? expiration.toEpochMilli() : null);
             if (!success) {
                 // The record already exists, so we need to overwrite via an update.
-                success = getStorageService().update(context, key, value, expiration);
+                success = getStorageService().update(context, key, value,
+                        expiration != null ? expiration.toEpochMilli() : null);
             }
         } while (!success && attempts-- > 0);
 
diff --git a/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/CreateGlobalConsentResult.java b/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/CreateGlobalConsentResult.java
index b794195..9cf9c95 100644
--- a/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/CreateGlobalConsentResult.java
+++ b/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/CreateGlobalConsentResult.java
@@ -58,7 +58,7 @@ public class CreateGlobalConsentResult extends AbstractConsentIndexedStorageActi
             final String value =
                     getStorageSerializer().serialize(Collections.singletonMap(globalConsent.getId(), globalConsent));
 
-            final long expiration = Instant.now().plus(getConsentFlowDescriptor().getLifetime()).toEpochMilli();
+            final Instant expiration = Instant.now().plus(getConsentFlowDescriptor().getLifetime());
 
             final ProfileInterceptorResult result =
                     new ConsentResult(getStorageContext(), getStorageKey(), value, expiration);
diff --git a/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/CreateResult.java b/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/CreateResult.java
index de58a29..4fdcc0b 100644
--- a/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/CreateResult.java
+++ b/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/flow/storage/impl/CreateResult.java
@@ -71,7 +71,7 @@ public class CreateResult extends AbstractConsentIndexedStorageAction {
             final Map<String, Consent> currentConsents = getConsentContext().getCurrentConsents();
             final String value = getStorageSerializer().serialize(currentConsents);
 
-            final long expiration = Instant.now().plus(getConsentFlowDescriptor().getLifetime()).toEpochMilli();
+            final Instant expiration = Instant.now().plus(getConsentFlowDescriptor().getLifetime());
 
             final ProfileInterceptorResult result =
                     new ConsentResult(getStorageContext(), getStorageKey(), value, expiration);
diff --git a/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/storage/impl/ConsentResult.java b/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/storage/impl/ConsentResult.java
index 5a8de34..8aeceb1 100644
--- a/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/storage/impl/ConsentResult.java
+++ b/idp-consent-impl/src/main/java/net/shibboleth/idp/consent/storage/impl/ConsentResult.java
@@ -17,15 +17,15 @@
 
 package net.shibboleth.idp.consent.storage.impl;
 
+import java.time.Instant;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import com.google.common.base.MoreObjects;
 
 import net.shibboleth.idp.profile.interceptor.AbstractProfileInterceptorResult;
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 
 /**
  * The result of a consent flow, suitable for storage.
@@ -44,7 +44,7 @@ public class ConsentResult extends AbstractProfileInterceptorResult {
             @Nonnull @NotEmpty final String context,
             @Nonnull @NotEmpty final String key,
             @Nonnull @NotEmpty final String value,
-            @Nullable @Positive @Duration final Long expiration) {
+            @Nullable final Instant expiration) {
         super(context, key, value, expiration);
     }
 
diff --git a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/config/SecurityConfiguration.java b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/config/SecurityConfiguration.java
index 83fcf74..ec6f9af 100644
--- a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/config/SecurityConfiguration.java
+++ b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/config/SecurityConfiguration.java
@@ -17,13 +17,11 @@
 
 package net.shibboleth.idp.profile.config;
 
-import java.util.concurrent.TimeUnit;
+import java.time.Duration;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.security.IdentifierGenerationStrategy;
 import net.shibboleth.utilities.java.support.security.SecureRandomIdentifierGenerationStrategy;
@@ -38,8 +36,8 @@ import org.opensaml.xmlsec.SignatureValidationConfiguration;
 /** Configuration for security behavior of profiles. */
 public class SecurityConfiguration {
 
-    /** Acceptable clock skew expressed in milliseconds. */
-    @Duration @Positive private final long clockSkew;
+    /** Acceptable clock skew. */
+    @Nonnull private final Duration clockSkew;
 
     /** Generator used to generate various secure IDs (e.g., message identifiers). */
     @Nonnull private final IdentifierGenerationStrategy idGenerator;
@@ -69,7 +67,7 @@ public class SecurityConfiguration {
      * {@link SecureRandomIdentifierGenerationStrategy} using the SHA1PRNG algorithm.
      */
     public SecurityConfiguration() {
-        clockSkew = TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES);
+        clockSkew = Duration.ofMinutes(5);
         idGenerator = new SecureRandomIdentifierGenerationStrategy();
     }
 
@@ -79,18 +77,20 @@ public class SecurityConfiguration {
      * @param skew the clock skew, must be greater than 0
      * @param generator the identifier generator, must not be null
      */
-    public SecurityConfiguration(@Duration @Positive final long skew,
-            @Nonnull final IdentifierGenerationStrategy generator) {
-        clockSkew = (int) Constraint.isGreaterThan(0, skew, "Clock skew must be greater than 0");
+    public SecurityConfiguration(@Nonnull final Duration skew, @Nonnull final IdentifierGenerationStrategy generator) {
+        Constraint.isNotNull(skew, "Clock skew cannot be null");
+        Constraint.isFalse(skew.isNegative() || skew.isZero(), "Clock skew must be greater than 0");
+        
+        clockSkew = skew;
         idGenerator = Constraint.isNotNull(generator, "Identifier generator cannot be null");
     }
 
     /**
-     * Get the acceptable clock skew expressed in milliseconds.
+     * Get the acceptable clock skew.
      * 
-     * @return acceptable clock skew expressed in milliseconds
+     * @return acceptable clock skew
      */
-    @Positive public long getClockSkew() {
+    @Nonnull public Duration getClockSkew() {
         return clockSkew;
     }
 
diff --git a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/AbstractProfileInterceptorResult.java b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/AbstractProfileInterceptorResult.java
index add34f6..708adb8 100644
--- a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/AbstractProfileInterceptorResult.java
+++ b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/AbstractProfileInterceptorResult.java
@@ -17,12 +17,12 @@
 
 package net.shibboleth.idp.profile.interceptor;
 
+import java.time.Instant;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
@@ -43,7 +43,7 @@ public abstract class AbstractProfileInterceptorResult extends AbstractIdentifia
     @Nonnull @NotEmpty private String storageValue;
 
     /** Storage expiration. */
-    @Nullable @Positive @Duration private Long storageExpiration;
+    @Nullable private Instant storageExpiration;
 
     /**
      * Constructor.
@@ -57,35 +57,36 @@ public abstract class AbstractProfileInterceptorResult extends AbstractIdentifia
             @Nonnull @NotEmpty final String context,
             @Nonnull @NotEmpty final String key,
             @Nonnull @NotEmpty final String value,
-            @Nullable @Positive @Duration final Long expiration) {
+            @Nullable final Instant expiration) {
 
         storageContext =
                 Constraint.isNotNull(StringSupport.trimOrNull(context), "Storage context cannot be null nor empty");
         storageKey = Constraint.isNotNull(StringSupport.trimOrNull(key), "Storage key cannot be null nor empty");
         storageValue = Constraint.isNotNull(StringSupport.trimOrNull(value), "Storage value cannot be null nor empty");
         if (expiration != null) {
-            storageExpiration = Constraint.isGreaterThan(0, expiration, "Storage expiration must be greater than 0");
+            Constraint.isGreaterThan(0, expiration.toEpochMilli(), "Storage expiration must be greater than 0");
+            storageExpiration = expiration;
         }
     }
 
     /** {@inheritDoc} */
-    @Override @Nonnull @NotEmpty public String getStorageContext() {
+    @Nonnull @NotEmpty public String getStorageContext() {
         return storageContext;
     }
 
     /** {@inheritDoc} */
-    @Override @Nonnull @NotEmpty public String getStorageKey() {
+    @Nonnull @NotEmpty public String getStorageKey() {
         return storageKey;
     }
 
     /** {@inheritDoc} */
-    @Override @Nonnull @NotEmpty public String getStorageValue() {
+    @Nonnull @NotEmpty public String getStorageValue() {
         return storageValue;
     }
 
     /** {@inheritDoc} */
-    @Override
-    @Nullable @Positive @Duration public Long getStorageExpiration() {
+    @Nullable public Instant getStorageExpiration() {
         return storageExpiration;
     }
-}
+    
+}
\ No newline at end of file
diff --git a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/ProfileInterceptorResult.java b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/ProfileInterceptorResult.java
index 1d6d904..47ee001 100644
--- a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/ProfileInterceptorResult.java
+++ b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/ProfileInterceptorResult.java
@@ -17,11 +17,12 @@
 
 package net.shibboleth.idp.profile.interceptor;
 
+import java.time.Instant;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.component.IdentifiedComponent;
 
 /**
@@ -56,5 +57,6 @@ public interface ProfileInterceptorResult extends IdentifiedComponent {
      * 
      * @return the storage expiration
      */
-    @Nullable @Positive Long getStorageExpiration();
-}
+    @Nullable Instant getStorageExpiration();
+    
+}
\ No newline at end of file
diff --git a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/logic/DateAttributePredicate.java b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/logic/DateAttributePredicate.java
index 0d43a80..2d7c38f 100644
--- a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/logic/DateAttributePredicate.java
+++ b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/logic/DateAttributePredicate.java
@@ -20,7 +20,6 @@ package net.shibboleth.idp.profile.logic;
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.idp.attribute.StringAttributeValue;
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.ParameterName;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
@@ -150,18 +149,6 @@ public class DateAttributePredicate extends AbstractAttributePredicate {
     }
 
     /**
-     * Set the system time offset in milliseconds, which affects the reference date for comparisons.
-     * 
-     * <p>By default all comparisons are against system time, i.e. zero offset.</p>
-     *
-     * @param offset System time offset. A negative value decreases the target date (sooner);
-     *                         a positive value increases the target date (later).
-     */
-    @Duration public void setOffset(@Duration final long offset) {
-        systemTimeOffset = java.time.Duration.ofMillis(offset);
-    }
-
-    /**
      * Set the system time offset, which affects the reference date for comparisons.
      * 
      * <p>By default all comparisons are against system time, i.e. zero offset.</p>
diff --git a/idp-profile-api/src/test/java/net/shibboleth/idp/profile/interceptor/AbstractProfileInterceptorResultTest.java b/idp-profile-api/src/test/java/net/shibboleth/idp/profile/interceptor/AbstractProfileInterceptorResultTest.java
index f1ba005..908658d 100644
--- a/idp-profile-api/src/test/java/net/shibboleth/idp/profile/interceptor/AbstractProfileInterceptorResultTest.java
+++ b/idp-profile-api/src/test/java/net/shibboleth/idp/profile/interceptor/AbstractProfileInterceptorResultTest.java
@@ -17,12 +17,12 @@
 
 package net.shibboleth.idp.profile.interceptor;
 
+import java.time.Instant;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
 
 import org.testng.Assert;
@@ -33,37 +33,37 @@ public class AbstractProfileInterceptorResultTest {
 
     @Test(expectedExceptions = ConstraintViolationException.class)
     public void testEmptyContext() {
-        new MockAbstractProfileInterceptorResult("", "key", "value", 100L);
+        new MockAbstractProfileInterceptorResult("", "key", "value", Instant.ofEpochMilli(100));
     }
 
     @Test(expectedExceptions = ConstraintViolationException.class)
     public void testEmptyKey() {
-        new MockAbstractProfileInterceptorResult("context", "", "value", 100L);
+        new MockAbstractProfileInterceptorResult("context", "", "value", Instant.ofEpochMilli(100));
     }
 
     @Test(expectedExceptions = ConstraintViolationException.class)
     public void testEmptyValue() {
-        new MockAbstractProfileInterceptorResult("context", "key", "", 100L);
+        new MockAbstractProfileInterceptorResult("context", "key", "", Instant.ofEpochMilli(100));
     }
 
     @Test(expectedExceptions = ConstraintViolationException.class)
     public void testNullContext() {
-        new MockAbstractProfileInterceptorResult(null, "key", "value", 100L);
+        new MockAbstractProfileInterceptorResult(null, "key", "value", Instant.ofEpochMilli(100));
     }
 
     @Test(expectedExceptions = ConstraintViolationException.class)
     public void testNullKey() {
-        new MockAbstractProfileInterceptorResult("context", null, "value", 100L);
+        new MockAbstractProfileInterceptorResult("context", null, "value", Instant.ofEpochMilli(100));
     }
 
     @Test(expectedExceptions = ConstraintViolationException.class)
     public void testNullValue() {
-        new MockAbstractProfileInterceptorResult("context", "key", null, 100L);
+        new MockAbstractProfileInterceptorResult("context", "key", null, Instant.ofEpochMilli(100));
     }
 
     @Test(expectedExceptions = ConstraintViolationException.class)
     public void testNegativeExpiration() {
-        new MockAbstractProfileInterceptorResult("context", "key", null, -100L);
+        new MockAbstractProfileInterceptorResult("context", "key", null, Instant.ofEpochMilli(-100));
     }
 
     @Test public void testNullExpiration() {
@@ -76,23 +76,23 @@ public class AbstractProfileInterceptorResultTest {
     }
 
     @Test(expectedExceptions = ConstraintViolationException.class) public void testZeroExpiration() {
-        new MockAbstractProfileInterceptorResult("context", "key", null, 0L);
+        new MockAbstractProfileInterceptorResult("context", "key", null, Instant.ofEpochMilli(0));
     }
 
     @Test public void testResult() {
         final MockAbstractProfileInterceptorResult result =
-                new MockAbstractProfileInterceptorResult("context", "key", "value", 100L);
+                new MockAbstractProfileInterceptorResult("context", "key", "value", Instant.ofEpochMilli(100));
         Assert.assertEquals(result.getStorageContext(), "context");
         Assert.assertEquals(result.getStorageKey(), "key");
         Assert.assertEquals(result.getStorageValue(), "value");
-        Assert.assertEquals(result.getStorageExpiration(), Long.valueOf(100));
+        Assert.assertEquals(result.getStorageExpiration(), Instant.ofEpochMilli(100));
     }
 
     private class MockAbstractProfileInterceptorResult extends AbstractProfileInterceptorResult {
 
         public MockAbstractProfileInterceptorResult(@Nonnull @NotEmpty final String context,
                 @Nonnull @NotEmpty final String key, @Nonnull @NotEmpty final String value,
-                @Nullable @Positive @Duration final Long expiration) {
+                @Nullable final Instant expiration) {
             super(context, key, value, expiration);
         }
     }
diff --git a/idp-profile-impl/src/main/java/net/shibboleth/idp/profile/interceptor/impl/WriteProfileInterceptorResultToStorage.java b/idp-profile-impl/src/main/java/net/shibboleth/idp/profile/interceptor/impl/WriteProfileInterceptorResultToStorage.java
index 75b6bfb..c8907d9 100644
--- a/idp-profile-impl/src/main/java/net/shibboleth/idp/profile/interceptor/impl/WriteProfileInterceptorResultToStorage.java
+++ b/idp-profile-impl/src/main/java/net/shibboleth/idp/profile/interceptor/impl/WriteProfileInterceptorResultToStorage.java
@@ -18,6 +18,7 @@
 package net.shibboleth.idp.profile.interceptor.impl;
 
 import java.io.IOException;
+import java.time.Instant;
 import java.util.List;
 
 import javax.annotation.Nonnull;
@@ -108,16 +109,17 @@ public class WriteProfileInterceptorResultToStorage extends AbstractProfileInter
         final String context = result.getStorageContext();
         final String key = result.getStorageKey();
         final String value = result.getStorageValue();
-        final Long expiration = result.getStorageExpiration();
+        final Instant expiration = result.getStorageExpiration();
 
         // Create / update loop until we succeed or exhaust attempts.
         int attempts = 10;
         boolean success = false;
         do {
-            success = storageService.create(context, key, value, expiration);
+            success = storageService.create(context, key, value, expiration != null ? expiration.toEpochMilli() : null);
             if (!success) {
                 // The record already exists, so we need to overwrite via an update.
-                success = storageService.update(context, key, value, expiration);
+                success = storageService.update(context, key, value,
+                        expiration != null ? expiration.toEpochMilli() : null);
             }
         } while (!success && attempts-- > 0);
 
diff --git a/idp-profile-impl/src/test/java/net/shibboleth/idp/profile/interceptor/impl/WriteProfileInterceptorResultToStorageTest.java b/idp-profile-impl/src/test/java/net/shibboleth/idp/profile/interceptor/impl/WriteProfileInterceptorResultToStorageTest.java
index b5281ef..cc92b0f 100644
--- a/idp-profile-impl/src/test/java/net/shibboleth/idp/profile/interceptor/impl/WriteProfileInterceptorResultToStorageTest.java
+++ b/idp-profile-impl/src/test/java/net/shibboleth/idp/profile/interceptor/impl/WriteProfileInterceptorResultToStorageTest.java
@@ -28,9 +28,7 @@ import net.shibboleth.idp.profile.context.ProfileInterceptorContext;
 import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
 import net.shibboleth.idp.profile.interceptor.AbstractProfileInterceptorResult;
 import net.shibboleth.idp.profile.interceptor.ProfileInterceptorFlowDescriptor;
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.storage.StorageRecord;
@@ -110,7 +108,7 @@ public class WriteProfileInterceptorResultToStorageTest {
     }
 
     @Test public void testCreateStorageRecordWithExpiration() throws Exception {
-        final Long expiration = Instant.now().plusSeconds(60).toEpochMilli();
+        final Instant expiration = Instant.now().plusSeconds(60);
         final MockProfileInterceptorResult result =
                 new MockProfileInterceptorResult("context", "key", "value", expiration);
         prc.getSubcontext(ProfileInterceptorContext.class).getResults().add(result);
@@ -122,11 +120,11 @@ public class WriteProfileInterceptorResultToStorageTest {
         final StorageRecord storageRecord = ss.read("context", "key");
         Assert.assertNotNull(storageRecord);
         Assert.assertEquals(storageRecord.getValue(), "value");
-        Assert.assertEquals(storageRecord.getExpiration(), expiration);
+        Assert.assertEquals(storageRecord.getExpiration(), Long.valueOf(expiration.toEpochMilli()));
     }
 
     @Test public void testUpdateStorageRecord() throws Exception {
-        final Long expiration = Instant.now().plusSeconds(60).toEpochMilli();
+        final Instant expiration = Instant.now().plusSeconds(60);
         MockProfileInterceptorResult result = new MockProfileInterceptorResult("context", "key", "value", null);
         prc.getSubcontext(ProfileInterceptorContext.class).getResults().add(result);
 
@@ -150,14 +148,14 @@ public class WriteProfileInterceptorResultToStorageTest {
         storageRecord = ss.read("context", "key");
         Assert.assertNotNull(storageRecord);
         Assert.assertEquals(storageRecord.getValue(), "value2");
-        Assert.assertEquals(storageRecord.getExpiration(), expiration);
+        Assert.assertEquals(storageRecord.getExpiration(), Long.valueOf(expiration.toEpochMilli()));
     }
 
     private class MockProfileInterceptorResult extends AbstractProfileInterceptorResult {
 
         public MockProfileInterceptorResult(@Nonnull @NotEmpty final String context,
                 @Nonnull @NotEmpty final String key, @Nonnull @NotEmpty final String value,
-                @Nullable @Positive @Duration final Long expiration) {
+                @Nullable final Instant expiration) {
             super(context, key, value, expiration);
         }
     }
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfiguration.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfiguration.java
index e535437..1ddc275 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfiguration.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfiguration.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.saml.profile.config;
 
+import java.time.Duration;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -29,11 +30,9 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import net.shibboleth.idp.profile.config.AbstractConditionalProfileConfiguration;
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
@@ -65,10 +64,10 @@ public abstract class AbstractSAMLProfileConfiguration extends AbstractCondition
     @Nullable private Predicate<ProfileRequestContext> includeNotBeforePredicate;
 
     /** Lookup function to supply {@link #assertionLifetime} property. */
-    @Nullable private Function<ProfileRequestContext,Long> assertionLifetimeLookupStrategy;
+    @Nullable private Function<ProfileRequestContext,Duration> assertionLifetimeLookupStrategy;
 
-    /** Lifetime of an assertion in milliseconds. Default value: 5 minutes */
-    @Positive @Duration private long assertionLifetime;
+    /** Lifetime of an assertion. Default value: 5 minutes */
+    @Nonnull private Duration assertionLifetime;
 
     /** Lookup function to supply {@link #assertionAudiences} property. */
     @Nullable private Function<ProfileRequestContext,Collection<String>> assertionAudiencesLookupStrategy;
@@ -88,7 +87,7 @@ public abstract class AbstractSAMLProfileConfiguration extends AbstractCondition
         signResponsesPredicate = Predicates.alwaysFalse();
         signAssertionsPredicate = Predicates.alwaysFalse();
         includeNotBeforePredicate = Predicates.alwaysTrue();
-        assertionLifetime = 5 * 60 * 1000;
+        assertionLifetime = Duration.ofMinutes(5);
         assertionAudiences = Collections.emptySet();
     }
 
@@ -104,7 +103,7 @@ public abstract class AbstractSAMLProfileConfiguration extends AbstractCondition
     }
     
     /** {@inheritDoc} */
-    @Override @Nonnull public Predicate<ProfileRequestContext> getSignAssertions() {
+    @Nonnull public Predicate<ProfileRequestContext> getSignAssertions() {
         return signAssertionsPredicate;
     }
 
@@ -119,7 +118,7 @@ public abstract class AbstractSAMLProfileConfiguration extends AbstractCondition
     }
 
     /** {@inheritDoc} */
-    @Override @Nonnull public Predicate<ProfileRequestContext> getSignRequests() {
+    @Nonnull public Predicate<ProfileRequestContext> getSignRequests() {
         return signRequestsPredicate;
     }
 
@@ -135,7 +134,7 @@ public abstract class AbstractSAMLProfileConfiguration extends AbstractCondition
     }
 
     /** {@inheritDoc} */
-    @Override @Nonnull public Predicate<ProfileRequestContext> getSignResponses() {
+    @Nonnull public Predicate<ProfileRequestContext> getSignResponses() {
         return signResponsesPredicate;
     }
 
@@ -150,18 +149,20 @@ public abstract class AbstractSAMLProfileConfiguration extends AbstractCondition
     }
 
     /** {@inheritDoc} */
-    @Override @Positive @Duration public long getAssertionLifetime() {
-        return Constraint.isGreaterThan(0, getIndirectProperty(assertionLifetimeLookupStrategy, assertionLifetime),
-                "Assertion lifetime must be greater than 0");
+    @Nonnull public Duration getAssertionLifetime() {
+        return getIndirectProperty(assertionLifetimeLookupStrategy, assertionLifetime);
     }
 
     /**
      * Set the lifetime of an assertion.
      * 
-     * @param lifetime lifetime of an assertion in milliseconds
+     * @param lifetime lifetime of an assertion
      */
-    @Duration public void setAssertionLifetime(@Positive @Duration final long lifetime) {
-        assertionLifetime = Constraint.isGreaterThan(0, lifetime, "Assertion lifetime must be greater than 0");
+    public void setAssertionLifetime(@Nonnull final Duration lifetime) {
+        Constraint.isNotNull(lifetime, "Assertion lifetime cannot be null");
+        Constraint.isFalse(lifetime.isNegative() || lifetime.isZero(), "Assertion lifetime must be greater than 0");
+        
+        assertionLifetime = lifetime;
     }
 
     /**
@@ -171,12 +172,12 @@ public abstract class AbstractSAMLProfileConfiguration extends AbstractCondition
      * 
      * @since 3.3.0
      */
-    public void setAssertionLifetimeLookupStrategy(@Nullable final Function<ProfileRequestContext,Long> strategy) {
+    public void setAssertionLifetimeLookupStrategy(@Nullable final Function<ProfileRequestContext,Duration> strategy) {
         assertionLifetimeLookupStrategy = strategy;
     }
 
     /**{@inheritDoc} */
-    @Override public boolean includeConditionsNotBefore() {
+    public boolean includeConditionsNotBefore() {
         return includeNotBeforePredicate.test(getProfileRequestContext());
     }
 
@@ -215,7 +216,7 @@ public abstract class AbstractSAMLProfileConfiguration extends AbstractCondition
     }
 
     /** {@inheritDoc} */
-    @Override @Nonnull @NonnullElements @NotLive public Set<String> getAdditionalAudiencesForAssertion() {
+    @Nonnull @NonnullElements @NotLive public Set<String> getAdditionalAudiencesForAssertion() {
         return ImmutableSet.copyOf(getIndirectProperty(assertionAudiencesLookupStrategy, assertionAudiences));
     }
 
@@ -224,22 +225,6 @@ public abstract class AbstractSAMLProfileConfiguration extends AbstractCondition
      * which an assertion may be shared.
      * 
      * @param audiences the additional audiences
-     * 
-     * @deprecated
-     */
-    @Deprecated
-    public void setAdditionalAudienceForAssertion(@Nonnull @NonnullElements final Collection<String> audiences) {
-        LoggerFactory.getLogger(AbstractSAMLProfileConfiguration.class).warn(
-                "Use of deprecated property name 'additionalAudienceForAssertion', please correct to "
-                    + "'additionalAudiencesForAssertion'");
-        setAdditionalAudiencesForAssertion(audiences);
-    }
-
-    /**
-     * Set the set of audiences, in addition to the relying party(ies) to which the IdP is issuing the assertion, with
-     * which an assertion may be shared.
-     * 
-     * @param audiences the additional audiences
      */
     public void setAdditionalAudiencesForAssertion(@Nullable @NonnullElements final Collection<String> audiences) {
 
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/DurationConfigurationLookupStrategy.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/DurationConfigurationLookupStrategy.java
index a8f2f08..920d581 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/DurationConfigurationLookupStrategy.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/DurationConfigurationLookupStrategy.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.saml.profile.config;
 
+import java.time.Duration;
 import java.util.List;
 
 import javax.annotation.Nonnull;
@@ -29,8 +30,9 @@ import org.opensaml.core.xml.schema.XSString;
 import org.opensaml.saml.saml2.core.Attribute;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.core.convert.converter.Converter;
 
-import net.shibboleth.ext.spring.config.DurationToLongConverter;
+import net.shibboleth.ext.spring.config.StringToDurationConverter;
 
 /**
  * A strategy function that examines SAML metadata associated with a relying party and derives Long-valued
@@ -38,22 +40,22 @@ import net.shibboleth.ext.spring.config.DurationToLongConverter;
  * 
  * @since 3.4.0
  */
-public class DurationConfigurationLookupStrategy extends AbstractMetadataDrivenConfigurationLookupStrategy<Long> {
+public class DurationConfigurationLookupStrategy extends AbstractMetadataDrivenConfigurationLookupStrategy<Duration> {
 
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(DurationConfigurationLookupStrategy.class);
 
     /** Converter to handle duration strings. */
-    @Nonnull private final DurationToLongConverter durationConverter;
+    @Nonnull private final Converter<String,Duration> durationConverter;
 
     /** Constructor. */
     public DurationConfigurationLookupStrategy() {
-        durationConverter = new DurationToLongConverter();
+        durationConverter = new StringToDurationConverter();
     }
 
     /** {@inheritDoc} */
     @Override
-    @Nullable protected Long doTranslate(@Nonnull final Attribute tag) {
+    @Nullable protected Duration doTranslate(@Nonnull final Attribute tag) {
         
         final List<XMLObject> values = tag.getAttributeValues();
         if (values.size() != 1) {
@@ -61,7 +63,7 @@ public class DurationConfigurationLookupStrategy extends AbstractMetadataDrivenC
             return null;
         }
         
-        log.debug("Converting tag '{}' to Long duration property", tag.getName());
+        log.debug("Converting tag '{}' to Duration property", tag.getName());
         return xmlObjectToDuration(values.get(0));
     }
     
@@ -73,7 +75,7 @@ public class DurationConfigurationLookupStrategy extends AbstractMetadataDrivenC
      * 
      * @return the converted value, or null
      */
-    @Nullable private Long xmlObjectToDuration(@Nonnull final XMLObject object) {
+    @Nullable private Duration xmlObjectToDuration(@Nonnull final XMLObject object) {
         if (object instanceof XSString) {
             final String value = ((XSString) object).getValue();
             if (value != null) {
@@ -88,7 +90,7 @@ public class DurationConfigurationLookupStrategy extends AbstractMetadataDrivenC
             }
         } else if (object instanceof XSInteger) {
             final Integer value = ((XSInteger) object).getValue();
-            return value != null ? value.longValue() : null;
+            return value != null ? Duration.ofMillis(value.longValue()) : null;
         } else if (object instanceof XSAny) {
             final XSAny wc = (XSAny) object;
             if (wc.getUnknownAttributes().isEmpty() && wc.getUnknownXMLObjects().isEmpty()) {
@@ -106,7 +108,7 @@ public class DurationConfigurationLookupStrategy extends AbstractMetadataDrivenC
             }
         }
         
-        log.error("Unsupported conversion to Long duration from XMLObject type ({})", object.getClass().getName());
+        log.error("Unsupported conversion to Duration from XMLObject type ({})", object.getClass().getName());
         return null;
     }
 // Checkstyle: CyclomaticComplexity ON
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/SAMLProfileConfiguration.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/SAMLProfileConfiguration.java
index 42f913d..e5b4a35 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/SAMLProfileConfiguration.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/SAMLProfileConfiguration.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.saml.profile.config;
 
+import java.time.Duration;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -27,7 +28,6 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import net.shibboleth.idp.profile.config.ProfileConfiguration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
 import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
 
 /** Common interface for SAML profile configurations. */
@@ -55,11 +55,11 @@ public interface SAMLProfileConfiguration extends ProfileConfiguration {
     @Nonnull Predicate<ProfileRequestContext> getSignResponses();
 
     /**
-     * Get the lifetime of generated assertions in milliseconds.
+     * Get the lifetime of generated assertions.
      * 
-     * @return lifetime of generated assertions in milliseconds
+     * @return lifetime of generated assertions
      */
-    @Positive long getAssertionLifetime();
+    @Nonnull Duration getAssertionLifetime();
 
     /**
      * Get an unmodifiable set of audiences, in addition to the relying party(ies) to which the IdP is issuing the
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/AssertionLifetimeLookupFunction.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/AssertionLifetimeLookupFunction.java
index ce6d5b6..a252936 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/AssertionLifetimeLookupFunction.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/AssertionLifetimeLookupFunction.java
@@ -43,7 +43,7 @@ public class AssertionLifetimeLookupFunction extends AbstractRelyingPartyLookupF
         if (rpc != null) {
             final ProfileConfiguration pc = rpc.getProfileConfig();
             if (pc != null && pc instanceof SAMLProfileConfiguration) {
-                return Duration.ofMillis(((SAMLProfileConfiguration) pc).getAssertionLifetime());
+                return ((SAMLProfileConfiguration) pc).getAssertionLifetime();
             }
         }
         
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/SessionLifetimeLookupFunction.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/SessionLifetimeLookupFunction.java
index 50813fb..37d1a02 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/SessionLifetimeLookupFunction.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/navigate/SessionLifetimeLookupFunction.java
@@ -17,6 +17,8 @@
 
 package net.shibboleth.idp.saml.profile.config.navigate;
 
+import java.time.Duration;
+
 import javax.annotation.Nullable;
 
 import net.shibboleth.idp.profile.config.ProfileConfiguration;
@@ -33,10 +35,10 @@ import org.opensaml.profile.context.ProfileRequestContext;
  * 
  * <p>If a specific setting is unavailable, zero is returned.</p>
  */
-public class SessionLifetimeLookupFunction extends AbstractRelyingPartyLookupFunction<Long> {
+public class SessionLifetimeLookupFunction extends AbstractRelyingPartyLookupFunction<Duration> {
 
     /** {@inheritDoc} */
-    @Nullable public Long apply(@Nullable final ProfileRequestContext input) {
+    @Nullable public Duration apply(@Nullable final ProfileRequestContext input) {
         final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
         if (rpc != null) {
             final ProfileConfiguration pc = rpc.getProfileConfig();
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
index 734938c..505f91c 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
@@ -18,6 +18,7 @@
 package net.shibboleth.idp.saml.saml2.profile.config;
 
 import java.security.Principal;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -32,7 +33,6 @@ import javax.annotation.Nullable;
 
 import net.shibboleth.idp.authn.config.AuthenticationProfileConfiguration;
 import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
@@ -53,7 +53,7 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
         implements AuthenticationProfileConfiguration {
     
     /** ID for this profile configuration. */
-    public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/saml2/sso/browser";
+    @Nonnull @NotEmpty public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/saml2/sso/browser";
         
     /** Bit constant for RequestedAuthnContext feature. */
     public static final int FEATURE_AUTHNCONTEXT = 0x1;
@@ -71,13 +71,10 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
     @Nonnull private Predicate<ProfileRequestContext> skipEndpointValidationWhenSignedPredicate;
 
     /** Lookup function to supply {@link #maximumSPSessionLifetime} property. */
-    @Nullable private Function<ProfileRequestContext,Long> maximumSPSessionLifetimeLookupStrategy;
+    @Nullable private Function<ProfileRequestContext,Duration> maximumSPSessionLifetimeLookupStrategy;
     
-    /**
-     * The maximum amount of time, in milliseconds, the service provider should maintain a session for the user. A value
-     * of 0 (the default) indicates no cap is put on the SP's session lifetime.
-     */
-    @Duration @NonNegative private long maximumSPSessionLifetime;
+    /** The maximum amount of time the service provider should maintain a session for the user. */
+    @Nullable private Duration maximumSPSessionLifetime;
 
     /** 
      * The predicate used to determine if produced assertions may be delegated.
@@ -133,7 +130,6 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
         includeAttributeStatementPredicate = Predicates.alwaysTrue();
         forceAuthnPredicate = Predicates.alwaysFalse();
         skipEndpointValidationWhenSignedPredicate = Predicates.alwaysFalse();
-        maximumSPSessionLifetime = 0;
         maximumTokenDelegationChainLength = 1;
         allowDelegationPredicate = Predicates.<ProfileRequestContext>alwaysFalse();
         defaultAuthenticationContexts = Collections.emptyList();
@@ -143,20 +139,6 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
     }
     
     /**
-     * Get whether attributes should be resolved during the profile.
-     *
-     * <p>Default is true</p>
-     * 
-     * @return true iff attributes should be resolved
-     * 
-     * @deprecated Use {@link #getResolveAttributesPredicate()} instead.
-     */
-    @Deprecated
-    public boolean resolveAttributes() {
-        return resolveAttributesPredicate.test(getProfileRequestContext());
-    }
-    
-    /**
      * Set whether attributes should be resolved during the profile.
      * 
      * @param flag flag to set
@@ -189,20 +171,6 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
     }
 
     /**
-     * Get whether responses to the authentication request should include an attribute statement.
-     *
-     * <p>Default is true</p>
-     *
-     * @return whether responses to the authentication request should include an attribute statement
-     * 
-     * @deprecated Use {@link #getIncludeAttributeStatementPredicate()} instead.
-     */
-    @Deprecated
-    public boolean includeAttributeStatement() {
-        return includeAttributeStatementPredicate.test(getProfileRequestContext());
-    }
-
-    /**
      * Set whether responses to the authentication request should include an attribute statement.
      * 
      * @param include flag to set
@@ -272,18 +240,6 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
     }
     
     /**
-     * Get whether the response endpoint should be validated if the request is signed.
-     * 
-     * @return whether the response endpoint should be validated if the request is signed
-     * 
-     * @deprecated Use {@link #getSkipEndpointValidationWhenSignedPredicate()} instead.
-     */
-    @Deprecated
-    public boolean skipEndpointValidationWhenSigned() {
-        return skipEndpointValidationWhenSignedPredicate.test(getProfileRequestContext());
-    }
-
-    /**
      * Set whether the response endpoint should be validated if the request is signed.
      * 
      * @param skip whether the response endpoint should be validated if the request is signed
@@ -318,27 +274,26 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
     }
 
     /**
-     * Get the maximum amount of time, in milliseconds, the service provider should maintain a session for the user
-     * based on the authentication assertion. A value of 0 is interpreted as an unlimited lifetime.
+     * Get the maximum amount of time the service provider should maintain a session for the user
+     * based on the authentication assertion. A null or 0 is interpreted as an unlimited lifetime.
      * 
      * @return max lifetime of service provider should maintain a session
      */
-    @NonNegative @Duration public long getMaximumSPSessionLifetime() {
-        return Constraint.isGreaterThanOrEqual(0,
-                getIndirectProperty(maximumSPSessionLifetimeLookupStrategy, maximumSPSessionLifetime),
-                "Maximum SP session lifetime must be greater or equal to 0");
+    @Nullable public Duration getMaximumSPSessionLifetime() {
+        return getIndirectProperty(maximumSPSessionLifetimeLookupStrategy, maximumSPSessionLifetime);
     }
 
     /**
-     * Set the maximum amount of time, in milliseconds, the service provider should maintain a session for the user
-     * based on the authentication assertion. A value of 0 is interpreted as an unlimited lifetime.
+     * Set the maximum amount of time the service provider should maintain a session for the user
+     * based on the authentication assertion. A null or 0 is interpreted as an unlimited lifetime.
      * 
      * @param lifetime max lifetime of service provider should maintain a session
      */
-    @Duration public void setMaximumSPSessionLifetime(@Duration @NonNegative final long lifetime) {
-        maximumSPSessionLifetime =
-                Constraint.isGreaterThanOrEqual(0, lifetime,
-                        "Maximum SP session lifetime must be greater than or equal to 0");
+    public void setMaximumSPSessionLifetime(@Nullable final Duration lifetime) {
+        Constraint.isFalse(lifetime != null && lifetime.isNegative(),
+                "Maximum SP session lifetime must be greater than or equal to 0");
+        
+        maximumSPSessionLifetime = lifetime;
     }
     
     /**
@@ -349,53 +304,15 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
      * @since 3.3.0
      */
     public void setMaximumSPSessionLifetimeLookupStrategy(
-            @Nullable final Function<ProfileRequestContext,Long> strategy) {
+            @Nullable final Function<ProfileRequestContext,Duration> strategy) {
         maximumSPSessionLifetimeLookupStrategy = strategy;
     }
-
-    /**
-     * Get whether produced assertions may be delegated.
-     * 
-     * @return whether produced assertions may be delegated, as a {@link Boolean}.  May be null.
-     * 
-     * @deprecated use instead {@link #getAllowDelegation()} predicate
-     */
-    @Deprecated
-    public Boolean getAllowingDelegation() {
-        return allowDelegationPredicate.test(getProfileRequestContext());
-    }
-
-    /**
-     * Set whether produced assertions may be delegated.
-     * 
-     * @param isAllowed whether produced assertions may be delegated
-     */
-    @Deprecated
-    public void setAllowingDelegation(final Boolean isAllowed) {
-        if (isAllowed != null) {
-            allowDelegationPredicate = isAllowed ? Predicates.<ProfileRequestContext>alwaysTrue()
-                    : Predicates.<ProfileRequestContext>alwaysFalse();
-        } else {
-            allowDelegationPredicate = Predicates.alwaysFalse();
-        }
-    }
-
-    /**
-     * Get whether produced assertions may be delegated.
-     * 
-     * @return whether produced assertions may be delegated
-     */
-    public boolean isAllowingDelegation() {
-        return allowDelegationPredicate.test(getProfileRequestContext());
-    }
-    
     
     /**
      * Get the predicate used to determine if produced assertions may be delegated.
      * 
      * @return predicate used to determine if produced assertions may be delegated
      */
-
     @Nonnull public Predicate<ProfileRequestContext> getAllowDelegation() {
         return allowDelegationPredicate;
     }
@@ -405,7 +322,6 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
      * 
      * @param  predicate used to determine if produced assertions may be delegated
      */
-
     public void setAllowDelegation(@Nonnull final Predicate<ProfileRequestContext> predicate) {
         allowDelegationPredicate = Constraint.isNotNull(predicate, "Allow delegation predicate cannot be null");
     }
diff --git a/idp-saml-api/src/test/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfigurationTest.java b/idp-saml-api/src/test/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfigurationTest.java
index 3f67cb9..7b783a0 100644
--- a/idp-saml-api/src/test/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfigurationTest.java
+++ b/idp-saml-api/src/test/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfigurationTest.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.saml.profile.config;
 
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
@@ -81,20 +82,20 @@ public class AbstractSAMLProfileConfigurationTest {
 
     @Test public void testAssertionLifetime() {
         final MockSAMLProfileConfiguration config = new MockSAMLProfileConfiguration();
-        Assert.assertTrue(config.getAssertionLifetime() > 0);
+        Assert.assertTrue(config.getAssertionLifetime().toMillis() > 0);
 
-        config.setAssertionLifetime(100);
-        Assert.assertEquals(config.getAssertionLifetime(), 100);
+        config.setAssertionLifetime(Duration.ofMillis(100));
+        Assert.assertEquals(config.getAssertionLifetime(), Duration.ofMillis(100));
 
         try {
-            config.setAssertionLifetime(0);
+            config.setAssertionLifetime(Duration.ZERO);
             Assert.fail();
         } catch (ConstraintViolationException e) {
             // expected this
         }
 
         try {
-            config.setAssertionLifetime(-100);
+            config.setAssertionLifetime(Duration.ofMillis(-100));
             Assert.fail();
         } catch (ConstraintViolationException e) {
             // expected this
@@ -103,11 +104,12 @@ public class AbstractSAMLProfileConfigurationTest {
 
     @Test public void testIndirectAssertionLifetime() {
         final MockSAMLProfileConfiguration config = new MockSAMLProfileConfiguration();
-        config.setAssertionLifetimeLookupStrategy(FunctionSupport.<ProfileRequestContext,Long>constant(500L));
-        Assert.assertEquals(config.getAssertionLifetime(), 500L);
+        config.setAssertionLifetimeLookupStrategy(
+                FunctionSupport.<ProfileRequestContext,Duration>constant(Duration.ofMillis(500)));
+        Assert.assertEquals(config.getAssertionLifetime(), Duration.ofMillis(500));
 
-        config.setAssertionLifetimeLookupStrategy(FunctionSupport.<ProfileRequestContext,Long>constant(null));
-        Assert.assertEquals(config.getAssertionLifetime(), 5 * 60 * 1000);
+        config.setAssertionLifetimeLookupStrategy(FunctionSupport.<ProfileRequestContext,Duration>constant(null));
+        Assert.assertEquals(config.getAssertionLifetime(), Duration.ofMinutes(5));
     }
 
     @Test public void testIncludeNotBefore() {
@@ -125,13 +127,12 @@ public class AbstractSAMLProfileConfigurationTest {
         Assert.assertFalse(config.includeConditionsNotBefore());
     }
 
-    @SuppressWarnings("deprecation")
     @Test public void testAdditionalAudiencesForAssertion() {
         final MockSAMLProfileConfiguration config = new MockSAMLProfileConfiguration();
         Assert.assertNotNull(config.getAdditionalAudiencesForAssertion());
         Assert.assertTrue(config.getAdditionalAudiencesForAssertion().isEmpty());
 
-        config.setAdditionalAudienceForAssertion(Arrays.asList("", null, " foo"));
+        config.setAdditionalAudiencesForAssertion(Arrays.asList("", null, " foo"));
 
         final Set<String> audiences = config.getAdditionalAudiencesForAssertion();
         Assert.assertNotNull(audiences);
@@ -145,7 +146,7 @@ public class AbstractSAMLProfileConfigurationTest {
             // expected this
         }
 
-        config.setAdditionalAudienceForAssertion(Collections.<String>emptyList());
+        config.setAdditionalAudiencesForAssertion(Collections.<String>emptyList());
         Assert.assertNotNull(config.getAdditionalAudiencesForAssertion());
         Assert.assertTrue(config.getAdditionalAudiencesForAssertion().isEmpty());
     }
diff --git a/idp-saml-api/src/test/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfigurationTest.java b/idp-saml-api/src/test/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfigurationTest.java
index 58146a3..89428da 100644
--- a/idp-saml-api/src/test/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfigurationTest.java
+++ b/idp-saml-api/src/test/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfigurationTest.java
@@ -23,6 +23,7 @@ import net.shibboleth.idp.saml.profile.config.SAMLArtifactConfiguration;
 import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
 import net.shibboleth.utilities.java.support.logic.FunctionSupport;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
@@ -48,97 +49,72 @@ public class BrowserSSOProfileConfigurationTest {
         Assert.assertEquals(config.getId(), BrowserSSOProfileConfiguration.PROFILE_ID);
     }
 
-    @SuppressWarnings("deprecation")
     @Test
     public void testResolveAttributes(){
         final BrowserSSOProfileConfiguration config = new BrowserSSOProfileConfiguration();
-        Assert.assertTrue(config.resolveAttributes());
+        Assert.assertTrue(config.getResolveAttributesPredicate().test(null));
         
         config.setResolveAttributes(false);
-        Assert.assertFalse(config.resolveAttributes());
+        Assert.assertFalse(config.getResolveAttributesPredicate().test(null));
     }
 
-    @SuppressWarnings("deprecation")
     @Test
     public void testIndirectResolveAttributes(){
         final BrowserSSOProfileConfiguration config = new BrowserSSOProfileConfiguration();
 
-        config.setResolveAttributesPredicate(Predicates.<ProfileRequestContext>alwaysFalse());
-        Assert.assertFalse(config.resolveAttributes());
+        config.setResolveAttributesPredicate(Predicates.alwaysFalse());
+        Assert.assertFalse(config.getResolveAttributesPredicate().test(null));
     }
 
-    @SuppressWarnings("deprecation")
     @Test
     public void testIncludeAttributeStatement(){
         final BrowserSSOProfileConfiguration config = new BrowserSSOProfileConfiguration();
-        Assert.assertTrue(config.includeAttributeStatement());
+        Assert.assertTrue(config.getIncludeAttributeStatementPredicate().test(null));
 
         config.setIncludeAttributeStatement(false);
-        Assert.assertFalse(config.includeAttributeStatement());
+        Assert.assertFalse(config.getIncludeAttributeStatementPredicate().test(null));
     }
 
-    @SuppressWarnings("deprecation")
     @Test
     public void testIndirectIncludeAttributeStatement(){
         final BrowserSSOProfileConfiguration config = new BrowserSSOProfileConfiguration();
 
-        config.setIncludeAttributeStatementPredicate(Predicates.<ProfileRequestContext>alwaysFalse());
-        Assert.assertFalse(config.includeAttributeStatement());
+        config.setIncludeAttributeStatementPredicate(Predicates.alwaysFalse());
+        Assert.assertFalse(config.getIncludeAttributeStatementPredicate().test(null));
     }
 
-    @SuppressWarnings("deprecation")
     @Test
     public void testSkipEndpointValidationWhenSigned() {
         BrowserSSOProfileConfiguration config = new BrowserSSOProfileConfiguration();
-        Assert.assertFalse(config.skipEndpointValidationWhenSigned());
+        Assert.assertFalse(config.getSkipEndpointValidationWhenSignedPredicate().test(null));
 
         config.setSkipEndpointValidationWhenSigned(true);
-        Assert.assertTrue(config.skipEndpointValidationWhenSigned());
+        Assert.assertTrue(config.getSkipEndpointValidationWhenSignedPredicate().test(null));
     }
 
-    @SuppressWarnings("deprecation")
     @Test
     public void testIndirectEndpointValidationWhenSigned(){
         final BrowserSSOProfileConfiguration config = new BrowserSSOProfileConfiguration();
 
-        config.setSkipEndpointValidationWhenSignedPredicate(Predicates.<ProfileRequestContext>alwaysTrue());
-        Assert.assertTrue(config.skipEndpointValidationWhenSigned());
+        config.setSkipEndpointValidationWhenSignedPredicate(Predicates.alwaysTrue());
+        Assert.assertTrue(config.getSkipEndpointValidationWhenSignedPredicate().test(null));
     }
     
     @Test
     public void testMaximumSPSessionLifeTime() {
         final BrowserSSOProfileConfiguration config = new BrowserSSOProfileConfiguration();
-        Assert.assertEquals(config.getMaximumSPSessionLifetime(), 0);
+        Assert.assertNull(config.getMaximumSPSessionLifetime());
 
-        config.setMaximumSPSessionLifetime(1000);
-        Assert.assertEquals(config.getMaximumSPSessionLifetime(), 1000);
+        config.setMaximumSPSessionLifetime(Duration.ofSeconds(1));
+        Assert.assertEquals(config.getMaximumSPSessionLifetime(), Duration.ofSeconds(1));
     }
     
     @Test
     public void testIndirectMaximumSPSessionLifeTime() {
         final BrowserSSOProfileConfiguration config = new BrowserSSOProfileConfiguration();
 
-        config.setMaximumSPSessionLifetimeLookupStrategy(FunctionSupport.<ProfileRequestContext,Long>constant(1000L));
-        Assert.assertEquals(config.getMaximumSPSessionLifetime(), 1000);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Test
-    public void testAllowingDelegation() {
-        // Note: testing the deprecated boolean value variant
-        final BrowserSSOProfileConfiguration config = new BrowserSSOProfileConfiguration();
-        Assert.assertFalse(config.isAllowingDelegation());
-        Assert.assertFalse(config.getAllowingDelegation());
-        
-        config.setAllowingDelegation(false);
-        Assert.assertFalse(config.isAllowingDelegation());
-        Assert.assertFalse(config.getAllowingDelegation());
-        Assert.assertEquals(config.getAllowingDelegation(), Boolean.FALSE);
-
-        config.setAllowingDelegation(true);
-        Assert.assertTrue(config.isAllowingDelegation());
-        Assert.assertTrue(config.getAllowingDelegation());
-        Assert.assertEquals(config.getAllowingDelegation(), Boolean.TRUE);
+        config.setMaximumSPSessionLifetimeLookupStrategy(FunctionSupport.constant(Duration.ofSeconds(1)));
+        Assert.assertEquals(config.getMaximumSPSessionLifetime(), Duration.ofSeconds(1));
     }
     
     @Test
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredTransientIdGenerationStrategy.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredTransientIdGenerationStrategy.java
index 2e5b8ef..612b7af 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredTransientIdGenerationStrategy.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/nameid/impl/StoredTransientIdGenerationStrategy.java
@@ -18,10 +18,11 @@
 package net.shibboleth.idp.saml.nameid.impl;
 
 import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
 
 import javax.annotation.Nonnull;
 
-import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
@@ -58,13 +59,13 @@ public class StoredTransientIdGenerationStrategy extends AbstractIdentifiableIni
     /** Size, in bytes, of the identifier. */
     private int idSize;
 
-    /** Length, in milliseconds, identifiers are valid. */
-    @Duration @Positive private long idLifetime;
+    /** Length identifiers are valid. */
+    @Nonnull private Duration idLifetime;
 
     /** Constructor. */
     public StoredTransientIdGenerationStrategy() {
         idSize = 16;
-        idLifetime = 1000 * 60 * 60 * 4;
+        idLifetime = Duration.ofHours(4);
     }
 
     /**
@@ -110,23 +111,26 @@ public class StoredTransientIdGenerationStrategy extends AbstractIdentifiableIni
     }
     
     /**
-     * Get the time, in milliseconds, ids are valid.
+     * Get the time ids are valid.
      * 
-     * @return  time, in milliseconds, ids are valid
+     * @return  time ids are valid
      */
-    @Positive public long getIdLifetime() {
+    @Nonnull public Duration getIdLifetime() {
         return idLifetime;
     }
 
     /**
-     * Set the time, in milliseconds, ids are valid.
+     * Set the time ids are valid.
      * 
-     * @param lifetime time, in milliseconds, ids are valid
+     * @param lifetime time ids are valid
      */
-    public void setIdLifetime(@Duration @Positive final long lifetime) {
+    public void setIdLifetime(@Nonnull final Duration lifetime) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
-        idLifetime = Constraint.isGreaterThan(0, lifetime, "ID lifetime must be positive");
+        Constraint.isNotNull(lifetime, "ID lifetime cannot be null");
+        Constraint.isFalse(lifetime.isNegative() || lifetime.isZero(), "ID lifetime must be greater than 0");
+        
+        idLifetime = lifetime;
     }
 
     /** {@inheritDoc} */
@@ -143,7 +147,6 @@ public class StoredTransientIdGenerationStrategy extends AbstractIdentifiableIni
     }
     
     /** {@inheritDoc} */
-    @Override
     @Nonnull @NotEmpty public String generate(@Nonnull @NotEmpty final String relyingPartyId,
             @Nonnull @NotEmpty final String principalName) throws SAMLException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
@@ -163,11 +166,11 @@ public class StoredTransientIdGenerationStrategy extends AbstractIdentifiableIni
     
             log.debug("Creating new transient ID '{}'", id);
     
-            final long expiration = System.currentTimeMillis() + idLifetime;
+            final Instant expiration = Instant.now().plus(idLifetime);
     
             int collisions = 0;
             while (collisions < 5) {
-                if (idStore.create(TransientIdParameters.CONTEXT, id, principalTokenId, expiration)) {
+                if (idStore.create(TransientIdParameters.CONTEXT, id, principalTokenId, expiration.toEpochMilli())) {
                     return id;
                 } else {
                     ++collisions;
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnStatementToAssertion.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnStatementToAssertion.java
index d24eb80..6d72132 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnStatementToAssertion.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnStatementToAssertion.java
@@ -18,6 +18,7 @@
 package net.shibboleth.idp.saml.saml2.profile.impl;
 
 import java.security.Principal;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.Set;
 import java.util.function.Function;
@@ -91,7 +92,7 @@ public class AddAuthnStatementToAssertion extends BaseAddAuthenticationStatement
     @NonnullAfterInit private Function<ProfileRequestContext,AuthnContextClassRefPrincipal> classRefLookupStrategy;
 
     /** Strategy used to determine SessionNotOnOrAfter value to set. */
-    @Nullable private Function<ProfileRequestContext,Long> sessionLifetimeLookupStrategy;
+    @Nullable private Function<ProfileRequestContext,Duration> sessionLifetimeLookupStrategy;
         
     /** Constructor. */
     public AddAuthnStatementToAssertion() {
@@ -127,7 +128,7 @@ public class AddAuthnStatementToAssertion extends BaseAddAuthenticationStatement
      * 
      * @param strategy lookup strategy
      */
-    public void setSessionLifetimeLookupStrategy(@Nullable final Function<ProfileRequestContext,Long> strategy) {
+    public void setSessionLifetimeLookupStrategy(@Nullable final Function<ProfileRequestContext,Duration> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
         sessionLifetimeLookupStrategy = strategy;
@@ -230,9 +231,9 @@ public class AddAuthnStatementToAssertion extends BaseAddAuthenticationStatement
         }
         
         if (sessionLifetimeLookupStrategy != null) {
-            final Long lifetime = sessionLifetimeLookupStrategy.apply(profileRequestContext);
-            if (lifetime != null && lifetime > 0) {
-                statement.setSessionNotOnOrAfter(Instant.now().plusMillis(lifetime));
+            final Duration lifetime = sessionLifetimeLookupStrategy.apply(profileRequestContext);
+            if (lifetime != null && lifetime.toMillis() > 0) {
+                statement.setSessionNotOnOrAfter(Instant.now().plus(lifetime));
             }
         }
         
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/delegation/impl/PopulateDelegationContextTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/delegation/impl/PopulateDelegationContextTest.java
index c2ac2e7..89b4243 100644
--- a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/delegation/impl/PopulateDelegationContextTest.java
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/delegation/impl/PopulateDelegationContextTest.java
@@ -377,26 +377,6 @@ public class PopulateDelegationContextTest extends OpenSAMLInitBaseTestCase {
         Assert.assertFalse(delegationContext.getSubjectConfirmationCredentials().isEmpty());
     }
     
-    @SuppressWarnings("deprecation")
-    @Test
-    public void testRequestedViaConditionsAllowedViaLegacyBoolean() throws Exception {
-        authnRequest.setConditions(buildDelegationRequestConditions());
-        
-        browserSSOProfileConfig.setAllowingDelegation(true);
-        
-        action.initialize();
-        final Event result = action.execute(rc);
-        ActionTestingSupport.assertProceedEvent(result);
-        
-        
-        DelegationContext delegationContext = prc.getSubcontext(DelegationContext.class);
-        Assert.assertNotNull(delegationContext);
-        Assert.assertEquals(delegationContext.isIssuingDelegatedAssertion(), true);
-        Assert.assertEquals(delegationContext.getDelegationRequested(), DelegationRequest.REQUESTED_REQUIRED);
-        Assert.assertNotNull(delegationContext.getSubjectConfirmationCredentials());
-        Assert.assertFalse(delegationContext.getSubjectConfirmationCredentials().isEmpty());
-    }
-    
     @Test
     public void testRequiredNoKeyDescriptors() throws Exception {
         samlMetadataContext.getRoleDescriptor().getKeyDescriptors().clear();
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnStatementToAssertionTest.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnStatementToAssertionTest.java
index a692c01..0074194 100644
--- a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnStatementToAssertionTest.java
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnStatementToAssertionTest.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.saml.saml2.profile.impl;
 
+import java.time.Duration;
 import java.time.Instant;
 import java.util.Arrays;
 
@@ -162,7 +163,7 @@ public class AddAuthnStatementToAssertionTest extends OpenSAMLInitBaseTestCase {
     /** Test that the authentication statement is properly added. */
     @Test public void testSessionNotOnOrAfter() {
         final BrowserSSOProfileConfiguration ssoConfig = new BrowserSSOProfileConfiguration();
-        ssoConfig.setMaximumSPSessionLifetime(60 * 60 * 1000);
+        ssoConfig.setMaximumSPSessionLifetime(Duration.ofHours(1));
         ssoConfig.setSecurityConfiguration(new SecurityConfiguration());
         prc.getSubcontext(RelyingPartyContext.class).setProfileConfig(ssoConfig);
         
diff --git a/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/ExtractActiveAuthenticationResults.java b/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/ExtractActiveAuthenticationResults.java
index 8e1d800..3cb1fb9 100644
--- a/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/ExtractActiveAuthenticationResults.java
+++ b/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/ExtractActiveAuthenticationResults.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.session.impl;
 
+import java.time.Duration;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
@@ -110,8 +111,8 @@ public class ExtractActiveAuthenticationResults extends AbstractAuthenticationAc
             authenticationContext.setHintedName(session.getPrincipalName());
         }
         
-        final long maxAge = authenticationContext.getMaxAge();
         final Instant now = Instant.now();
+        final Duration maxAge = authenticationContext.getMaxAge();
         
         final List<AuthenticationResult> actives = new ArrayList<>();
         for (final AuthenticationResult result : session.getAuthenticationResults()) {
@@ -124,7 +125,7 @@ public class ExtractActiveAuthenticationResults extends AbstractAuthenticationAc
             }
             
             if (descriptor.isResultActive(result)) {
-                if (maxAge > 0 && result.getAuthenticationInstant().plusMillis(maxAge).isBefore(now)) {
+                if (maxAge != null && result.getAuthenticationInstant().plus(maxAge).isBefore(now)) {
                     log.debug("{} Authentication result {} exceeds maxAge setting, skipping it", getLogPrefix(),
                             result.getAuthenticationFlowId());
                     continue;
diff --git a/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/StorageBackedIdPSession.java b/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/StorageBackedIdPSession.java
index 7e1dc7b..862e2d0 100644
--- a/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/StorageBackedIdPSession.java
+++ b/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/StorageBackedIdPSession.java
@@ -254,8 +254,8 @@ public class StorageBackedIdPSession extends AbstractIdPSession {
         if (flow != null) {
             try {
                 if (!sessionManager.getStorageService().updateExpiration(getId(), result.getAuthenticationFlowId(),
-                        result.getLastActivityInstant().toEpochMilli() + flow.getInactivityTimeout()
-                            + AuthenticationFlowDescriptor.STORAGE_EXPIRATION_OFFSET)) {
+                        result.getLastActivityInstant().plus(flow.getInactivityTimeout()).plus(
+                            AuthenticationFlowDescriptor.STORAGE_EXPIRATION_OFFSET).toEpochMilli())) {
                     log.warn("Skipping update, AuthenticationResult for flow {} in session {} not found in storage",
                             flowId, getId());
                 }
@@ -585,13 +585,13 @@ public class StorageBackedIdPSession extends AbstractIdPSession {
             boolean success = false;
             do {
                 success = sessionManager.getStorageService().create(getId(), flowId, result, flow,
-                        result.getLastActivityInstant().toEpochMilli() + flow.getInactivityTimeout()
-                            + AuthenticationFlowDescriptor.STORAGE_EXPIRATION_OFFSET);
+                        result.getLastActivityInstant().plus(flow.getInactivityTimeout()).plus(
+                            AuthenticationFlowDescriptor.STORAGE_EXPIRATION_OFFSET).toEpochMilli());
                 if (!success) {
                     // The record already exists, so we need to overwrite via an update.
                     success = sessionManager.getStorageService().update(getId(), flowId, result, flow,
-                            result.getLastActivityInstant().toEpochMilli() + flow.getInactivityTimeout()
-                                + AuthenticationFlowDescriptor.STORAGE_EXPIRATION_OFFSET);
+                            result.getLastActivityInstant().plus(flow.getInactivityTimeout()).plus(
+                                AuthenticationFlowDescriptor.STORAGE_EXPIRATION_OFFSET).toEpochMilli());
                 }
             } while (!success && attempts-- > 0);
             
diff --git a/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/StorageBackedSessionManagerTest.java b/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/StorageBackedSessionManagerTest.java
index 9d14028..bf80755 100644
--- a/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/StorageBackedSessionManagerTest.java
+++ b/idp-session-impl/src/test/java/net/shibboleth/idp/session/impl/StorageBackedSessionManagerTest.java
@@ -80,15 +80,15 @@ public class StorageBackedSessionManagerTest extends SessionManagerBaseTestCase
         
         AuthenticationFlowDescriptor foo = new AuthenticationFlowDescriptor();
         foo.setId("AuthenticationFlow/Foo");
-        foo.setLifetime(60 * 1000);
-        foo.setInactivityTimeout(60 * 1000);
+        foo.setLifetime(Duration.ofMinutes(1));
+        foo.setInactivityTimeout(Duration.ofMinutes(1));
         foo.setResultSerializer(resultSerializer);
         foo.initialize();
         
         AuthenticationFlowDescriptor bar = new AuthenticationFlowDescriptor();
         bar.setId("AuthenticationFlow/Bar");
-        bar.setLifetime(60 * 1000);
-        bar.setInactivityTimeout(60 * 1000);
+        bar.setLifetime(Duration.ofMinutes(1));
+        bar.setInactivityTimeout(Duration.ofMinutes(1));
         bar.setResultSerializer(resultSerializer);
         bar.initialize();
         

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list