NIFI-9322 Refactored OIDC and SAML Access Resources

- Removed parent AccessResource from OIDCAccessResource and SAMLAccessResource to avoid unexpected inherited methods
- Moved Token Expiration validation from AccessResource to StandardBearerTokenProvider

Signed-off-by: Nathan Gough <thenatog@gmail.com>

This closes #5489.
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
index b184518..4f29cde 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
@@ -106,7 +106,7 @@
     private BearerTokenResolver bearerTokenResolver;
     private KnoxService knoxService;
     private KerberosService kerberosService;
-    protected LogoutRequestManager logoutRequestManager;
+    private LogoutRequestManager logoutRequestManager;
 
     /**
      * Retrieves the access configuration for this NiFi.
@@ -348,8 +348,7 @@
                 final String expirationFromProperties = properties.getKerberosAuthenticationExpiration();
                 long expiration = Math.round(FormatUtils.getPreciseTimeDuration(expirationFromProperties, TimeUnit.MILLISECONDS));
                 final String rawIdentity = authentication.getName();
-                String mappedIdentity = IdentityMappingUtil.mapIdentity(rawIdentity, IdentityMappingUtil.getIdentityMappings(properties));
-                expiration = validateTokenExpiration(expiration, mappedIdentity);
+                final String mappedIdentity = IdentityMappingUtil.mapIdentity(rawIdentity, IdentityMappingUtil.getIdentityMappings(properties));
 
                 final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(mappedIdentity, expiration, "KerberosService");
                 final String token = bearerTokenProvider.getBearerToken(loginAuthenticationToken);
@@ -416,8 +415,8 @@
             // attempt to authenticate
             final AuthenticationResponse authenticationResponse = loginIdentityProvider.authenticate(new LoginCredentials(username, password));
             final String rawIdentity = authenticationResponse.getIdentity();
-            String mappedIdentity = IdentityMappingUtil.mapIdentity(rawIdentity, IdentityMappingUtil.getIdentityMappings(properties));
-            long expiration = validateTokenExpiration(authenticationResponse.getExpiration(), mappedIdentity);
+            final String mappedIdentity = IdentityMappingUtil.mapIdentity(rawIdentity, IdentityMappingUtil.getIdentityMappings(properties));
+            final long expiration = authenticationResponse.getExpiration();
 
             // create the authentication token
             loginAuthenticationToken = new LoginAuthenticationToken(mappedIdentity, expiration, authenticationResponse.getIssuer());
@@ -506,7 +505,7 @@
         httpServletResponse.sendRedirect(getNiFiLogoutCompleteUri());
     }
 
-    LogoutRequest completeLogoutRequest(final HttpServletResponse httpServletResponse) {
+    private LogoutRequest completeLogoutRequest(final HttpServletResponse httpServletResponse) {
         LogoutRequest logoutRequest = null;
 
         final Optional<String> cookieValue = getLogoutRequestIdentifier();
@@ -522,24 +521,7 @@
         return logoutRequest;
     }
 
-    long validateTokenExpiration(long proposedTokenExpiration, String identity) {
-        final long maxExpiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS);
-        final long minExpiration = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
-
-        if (proposedTokenExpiration > maxExpiration) {
-            logger.warn(String.format("Max token expiration exceeded. Setting expiration to %s from %s for %s", maxExpiration,
-                    proposedTokenExpiration, identity));
-            proposedTokenExpiration = maxExpiration;
-        } else if (proposedTokenExpiration < minExpiration) {
-            logger.warn(String.format("Min token expiration not met. Setting expiration to %s from %s for %s", minExpiration,
-                    proposedTokenExpiration, identity));
-            proposedTokenExpiration = minExpiration;
-        }
-
-        return proposedTokenExpiration;
-    }
-
-    String getNiFiLogoutCompleteUri() {
+    private String getNiFiLogoutCompleteUri() {
         return getNiFiUri() + "logout-complete";
     }
 
@@ -548,7 +530,7 @@
      *
      * @param httpServletResponse HTTP Servlet Response
      */
-    protected void removeLogoutRequestCookie(final HttpServletResponse httpServletResponse) {
+    private void removeLogoutRequestCookie(final HttpServletResponse httpServletResponse) {
         applicationCookieService.removeCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.LOGOUT_REQUEST_IDENTIFIER);
     }
 
@@ -557,7 +539,7 @@
      *
      * @return Optional Logout Request Identifier
      */
-    protected Optional<String> getLogoutRequestIdentifier() {
+    private Optional<String> getLogoutRequestIdentifier() {
         return applicationCookieService.getCookieValue(httpServletRequest, ApplicationCookieName.LOGOUT_REQUEST_IDENTIFIER);
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
index 673e529..cc952a7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
@@ -74,7 +74,7 @@
         value = OIDCEndpoints.OIDC_ACCESS_ROOT,
         description = "Endpoints for obtaining an access token or checking access status."
 )
-public class OIDCAccessResource extends AccessResource {
+public class OIDCAccessResource extends ApplicationResource {
 
     private static final Logger logger = LoggerFactory.getLogger(OIDCAccessResource.class);
     private static final String OIDC_ID_TOKEN_AUTHN_ERROR = "Unable to exchange authorization for ID token: ";
@@ -115,7 +115,7 @@
     public void oidcRequest(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) throws Exception {
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
-            forwardToLoginMessagePage(httpServletRequest, httpServletResponse, AUTHENTICATION_NOT_ENABLED_MSG);
+            forwardToLoginMessagePage(httpServletRequest, httpServletResponse, AccessResource.AUTHENTICATION_NOT_ENABLED_MSG);
             return;
         }
 
@@ -199,7 +199,7 @@
     public Response oidcExchange(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) {
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
-            throw new AuthenticationNotSupportedException(AUTHENTICATION_NOT_ENABLED_MSG);
+            throw new AuthenticationNotSupportedException(AccessResource.AUTHENTICATION_NOT_ENABLED_MSG);
         }
 
         // ensure oidc is enabled
@@ -238,7 +238,7 @@
     )
     public void oidcLogout(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) throws Exception {
         if (!httpServletRequest.isSecure()) {
-            throw new IllegalStateException(AUTHENTICATION_NOT_ENABLED_MSG);
+            throw new IllegalStateException(AccessResource.AUTHENTICATION_NOT_ENABLED_MSG);
         }
 
         if (!oidcService.isOidcEnabled()) {
@@ -468,7 +468,7 @@
 
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
-            forwardToMessagePage(httpServletRequest, httpServletResponse, pageTitle, AUTHENTICATION_NOT_ENABLED_MSG);
+            forwardToMessagePage(httpServletRequest, httpServletResponse, pageTitle, AccessResource.AUTHENTICATION_NOT_ENABLED_MSG);
             return null;
         }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SAMLAccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SAMLAccessResource.java
index a374428..537aa0b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SAMLAccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SAMLAccessResource.java
@@ -27,6 +27,7 @@
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.api.cookie.ApplicationCookieName;
 import org.apache.nifi.web.security.logout.LogoutRequest;
+import org.apache.nifi.web.security.logout.LogoutRequestManager;
 import org.apache.nifi.web.security.saml.SAMLCredentialStore;
 import org.apache.nifi.web.security.saml.SAMLEndpoints;
 import org.apache.nifi.web.security.saml.SAMLService;
@@ -61,7 +62,7 @@
         value = SAMLEndpoints.SAML_ACCESS_ROOT,
         description = "Endpoints for authenticating, obtaining an access token or logging out of a configured SAML authentication provider."
 )
-public class SAMLAccessResource extends AccessResource {
+public class SAMLAccessResource extends ApplicationResource {
 
     private static final Logger logger = LoggerFactory.getLogger(SAMLAccessResource.class);
     private static final String SAML_METADATA_MEDIA_TYPE = "application/samlmetadata+xml";
@@ -73,6 +74,7 @@
     private SAMLStateManager samlStateManager;
     private SAMLCredentialStore samlCredentialStore;
     private IdpUserGroupService idpUserGroupService;
+    private LogoutRequestManager logoutRequestManager;
 
     @GET
     @Consumes(MediaType.WILDCARD)
@@ -85,7 +87,7 @@
     public Response samlMetadata(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) {
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
-            throw new AuthenticationNotSupportedException(AUTHENTICATION_NOT_ENABLED_MSG);
+            throw new AuthenticationNotSupportedException(AccessResource.AUTHENTICATION_NOT_ENABLED_MSG);
         }
 
         // ensure saml is enabled
@@ -212,7 +214,7 @@
         // create the login token
         final String rawIdentity = samlService.getUserIdentity(samlCredential);
         final String mappedIdentity = IdentityMappingUtil.mapIdentity(rawIdentity, IdentityMappingUtil.getIdentityMappings(properties));
-        final long expiration = validateTokenExpiration(samlService.getAuthExpiration(), mappedIdentity);
+        final long expiration = samlService.getAuthExpiration();
         final String issuer = samlCredential.getRemoteEntityID();
 
         final LoginAuthenticationToken loginToken = new LoginAuthenticationToken(mappedIdentity, mappedIdentity, expiration, issuer);
@@ -255,7 +257,7 @@
 
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
-            throw new AuthenticationNotSupportedException(AUTHENTICATION_NOT_ENABLED_MSG);
+            throw new AuthenticationNotSupportedException(AccessResource.AUTHENTICATION_NOT_ENABLED_MSG);
         }
 
         // ensure saml is enabled
@@ -446,19 +448,21 @@
         assert(isSamlEnabled(httpServletRequest, httpServletResponse, !LOGGING_IN));
 
         // complete the logout request if one exists
-        final LogoutRequest completedLogoutRequest = completeLogoutRequest(httpServletResponse);
+        final Optional<String> cookieValue = getLogoutRequestIdentifier();
+        if (cookieValue.isPresent()) {
+            final String logoutRequestIdentifier = cookieValue.get();
+            final LogoutRequest logoutRequest = logoutRequestManager.complete(logoutRequestIdentifier);
 
-        // if a logout request was completed, then delete the stored SAMLCredential for that user
-        if (completedLogoutRequest != null) {
-            final String userIdentity = completedLogoutRequest.getMappedUserIdentity();
+            final String mappedUserIdentity = logoutRequest.getMappedUserIdentity();
+            samlCredentialStore.delete(mappedUserIdentity);
+            idpUserGroupService.deleteUserGroups(mappedUserIdentity);
 
-            logger.info("Removing cached SAML information for " + userIdentity);
-            samlCredentialStore.delete(userIdentity);
-
-            logger.info("Removing cached SAML Groups for " + userIdentity);
-            idpUserGroupService.deleteUserGroups(userIdentity);
+            logger.info("Logout Request [{}] Identity [{}] SAML Local Logout Completed", logoutRequestIdentifier, mappedUserIdentity);
+        } else {
+            logger.warn("Logout Request Cookie [{}] not found", ApplicationCookieName.LOGOUT_REQUEST_IDENTIFIER.getCookieName());
         }
 
+        removeLogoutRequestCookie(httpServletResponse);
         // redirect to logout landing page
         httpServletResponse.sendRedirect(getNiFiLogoutCompleteUri());
     }
@@ -488,7 +492,7 @@
 
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
-            forwardToMessagePage(httpServletRequest, httpServletResponse, pageTitle, AUTHENTICATION_NOT_ENABLED_MSG);
+            forwardToMessagePage(httpServletRequest, httpServletResponse, pageTitle, AccessResource.AUTHENTICATION_NOT_ENABLED_MSG);
             return false;
         }
 
@@ -508,6 +512,18 @@
         return applicationCookieService.getCookieValue(httpServletRequest, ApplicationCookieName.SAML_REQUEST_IDENTIFIER);
     }
 
+    private void removeLogoutRequestCookie(final HttpServletResponse httpServletResponse) {
+        applicationCookieService.removeCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.LOGOUT_REQUEST_IDENTIFIER);
+    }
+
+    private Optional<String> getLogoutRequestIdentifier() {
+        return applicationCookieService.getCookieValue(httpServletRequest, ApplicationCookieName.LOGOUT_REQUEST_IDENTIFIER);
+    }
+
+    private String getNiFiLogoutCompleteUri() {
+        return getNiFiUri() + "logout-complete";
+    }
+
     public void setSamlService(SAMLService samlService) {
         this.samlService = samlService;
     }
@@ -531,4 +547,8 @@
     protected NiFiProperties getProperties() {
         return properties;
     }
+
+    public void setLogoutRequestManager(LogoutRequestManager logoutRequestManager) {
+        this.logoutRequestManager = logoutRequestManager;
+    }
 }
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProvider.java
index 95185b6..aca6e40 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProvider.java
@@ -32,6 +32,8 @@
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Date;
 import java.util.Objects;
 import java.util.UUID;
@@ -44,6 +46,10 @@
 
     private static final String URL_ENCODED_CHARACTER_SET = StandardCharsets.UTF_8.name();
 
+    private static final Duration MAXIMUM_EXPIRATION = Duration.ofHours(12);
+
+    private static final Duration MINIMUM_EXPIRATION = Duration.ofMinutes(1);
+
     private final JwsSignerProvider jwsSignerProvider;
 
     public StandardBearerTokenProvider(final JwsSignerProvider jwsSignerProvider) {
@@ -64,7 +70,7 @@
 
         final String issuer = getUrlEncoded(loginAuthenticationToken.getIssuer());
         final Date now = new Date();
-        final Date expirationTime = new Date(loginAuthenticationToken.getExpiration());
+        final Date expirationTime = getExpirationTime(loginAuthenticationToken);
         final JWTClaimsSet claims = new JWTClaimsSet.Builder()
                 .jwtID(UUID.randomUUID().toString())
                 .subject(subject)
@@ -78,6 +84,24 @@
         return getSignedBearerToken(claims);
     }
 
+    private Date getExpirationTime(final LoginAuthenticationToken loginAuthenticationToken) {
+        Instant expiration = Instant.ofEpochMilli(loginAuthenticationToken.getExpiration());
+
+        final Instant maximumExpiration = Instant.now().plus(MAXIMUM_EXPIRATION);
+        final Instant minimumExpiration = Instant.now().plus(MINIMUM_EXPIRATION);
+
+        final String identity = loginAuthenticationToken.getName();
+        if (expiration.isAfter(maximumExpiration)) {
+            LOGGER.warn("Identity [{}] Token Expiration [{}] greater than maximum [{}]", identity, expiration, MAXIMUM_EXPIRATION);
+            expiration = maximumExpiration;
+        } else if (expiration.isBefore(minimumExpiration)) {
+            LOGGER.warn("Identity [{}] Token Expiration [{}] less than minimum [{}]", identity, expiration, MINIMUM_EXPIRATION);
+            expiration = minimumExpiration;
+        }
+
+        return Date.from(expiration);
+    }
+
     private String getSignedBearerToken(final JWTClaimsSet claims) {
         final Date expirationTime = claims.getExpirationTime();
         final JwsSignerContainer jwsSignerContainer = jwsSignerProvider.getJwsSignerContainer(expirationTime.toInstant());
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
index 3591239..c0e895c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java
@@ -19,8 +19,7 @@
 import org.apache.nifi.security.util.CertificateUtils;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
+import java.time.Instant;
 
 /**
  * This is an Authentication Token for logging in. Once a user is authenticated, they can be issued an ID token.
@@ -57,8 +56,7 @@
         this.identity = identity;
         this.username = username;
         this.issuer = issuer;
-        Calendar now = Calendar.getInstance();
-        this.expiration = now.getTimeInMillis() + expiration;
+        this.expiration = Instant.now().plusMillis(expiration).toEpochMilli();
     }
 
     @Override
@@ -98,20 +96,15 @@
 
     @Override
     public String toString() {
-        Calendar expirationTime = Calendar.getInstance();
-        expirationTime.setTimeInMillis(getExpiration());
-        long remainingTime = expirationTime.getTimeInMillis() - Calendar.getInstance().getTimeInMillis();
-
-        SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSS");
-        dateFormat.setTimeZone(expirationTime.getTimeZone());
-        String expirationTimeString = dateFormat.format(expirationTime.getTime());
+        final Instant expirationTime = Instant.ofEpochMilli(expiration);
+        long remainingTime = expirationTime.toEpochMilli() - Instant.now().toEpochMilli();
 
         return new StringBuilder("LoginAuthenticationToken for ")
                 .append(getName())
                 .append(" issued by ")
                 .append(getIssuer())
                 .append(" expiring at ")
-                .append(expirationTimeString)
+                .append(expirationTime)
                 .append(" [")
                 .append(getExpiration())
                 .append(" ms, ")
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java
index 668d04c..da3bb20 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/provider/StandardBearerTokenProviderTest.java
@@ -27,34 +27,41 @@
 import org.apache.nifi.web.security.jwt.jws.JwsSignerContainer;
 import org.apache.nifi.web.security.jwt.jws.JwsSignerProvider;
 import org.apache.nifi.web.security.token.LoginAuthenticationToken;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.jupiter.MockitoExtension;
 
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
 import java.security.interfaces.RSAPublicKey;
 import java.text.ParseException;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.Collections;
+import java.util.Date;
 import java.util.UUID;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.when;
 
-@RunWith(MockitoJUnitRunner.class)
+@ExtendWith(MockitoExtension.class)
 public class StandardBearerTokenProviderTest {
     private static final String USERNAME = "USERNAME";
 
     private static final String IDENTITY = "IDENTITY";
 
-    private static final long EXPIRATION = 60;
+    private static final Duration EXPIRATION = Duration.ofHours(1);
+
+    private static final Duration MAXIMUM_DURATION_EXCEEDED = Duration.parse("PT12H5M");
+
+    private static final Duration MINIMUM_DURATION_EXCEEDED = Duration.parse("PT30S");
 
     private static final String ISSUER = "ISSUER";
 
@@ -73,7 +80,7 @@
 
     private JWSSigner jwsSigner;
 
-    @Before
+    @BeforeEach
     public void setProvider() throws NoSuchAlgorithmException {
         provider = new StandardBearerTokenProvider(jwsSignerProvider);
 
@@ -86,24 +93,76 @@
 
     @Test
     public void testGetBearerToken() throws ParseException, JOSEException {
-        final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, USERNAME, EXPIRATION, ISSUER);
-        final String keyIdentifier = UUID.randomUUID().toString();
-        final JwsSignerContainer jwsSignerContainer = new JwsSignerContainer(keyIdentifier, JWS_ALGORITHM, jwsSigner);
-        when(jwsSignerProvider.getJwsSignerContainer(isA(Instant.class))).thenReturn(jwsSignerContainer);
+        final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, USERNAME, EXPIRATION.toMillis(), ISSUER);
+        setSignerProvider();
 
         final String bearerToken = provider.getBearerToken(loginAuthenticationToken);
 
-        final SignedJWT signedJwt = SignedJWT.parse(bearerToken);
-        assertTrue("Verification Failed", signedJwt.verify(jwsVerifier));
-
+        final SignedJWT signedJwt = assertTokenVerified(bearerToken);
         final JWTClaimsSet claims = signedJwt.getJWTClaimsSet();
-        assertNotNull("Issue Time not found", claims.getIssueTime());
-        assertNotNull("Not Before Time Time not found", claims.getNotBeforeTime());
-        assertNotNull("Expiration Time Time not found", claims.getExpirationTime());
+        assertNotNull(claims.getIssueTime(), "Issue Time not found");
+        assertNotNull(claims.getNotBeforeTime(), "Not Before Time not found");
+
+        final Date claimExpirationTime = claims.getExpirationTime();
+        assertNotNull(claimExpirationTime, "Expiration Time not found");
+
+        final Date loginExpirationTime = new Date(loginAuthenticationToken.getExpiration());
+        assertEquals(loginExpirationTime.toString(), claimExpirationTime.toString(), "Expiration Time not matched");
+
         assertEquals(ISSUER, claims.getIssuer());
         assertEquals(Collections.singletonList(ISSUER), claims.getAudience());
         assertEquals(IDENTITY, claims.getSubject());
         assertEquals(USERNAME, claims.getClaim(SupportedClaim.PREFERRED_USERNAME.getClaim()));
         assertNotNull("JSON Web Token Identifier not found", claims.getJWTID());
     }
+
+    @Test
+    public void testGetBearerTokenExpirationMaximum() throws ParseException, JOSEException {
+        final long expiration = MAXIMUM_DURATION_EXCEEDED.toMillis();
+        final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, USERNAME, expiration, ISSUER);
+        setSignerProvider();
+
+        final String bearerToken = provider.getBearerToken(loginAuthenticationToken);
+
+        final SignedJWT signedJwt = assertTokenVerified(bearerToken);
+        final JWTClaimsSet claims = signedJwt.getJWTClaimsSet();
+        final Date claimExpirationTime = claims.getExpirationTime();
+        assertNotNull(claimExpirationTime, "Expiration Time not found");
+
+        final Date loginExpirationTime = new Date(loginAuthenticationToken.getExpiration());
+        assertNotSame(loginExpirationTime.toString(), claimExpirationTime.toString(), "Expiration Time matched");
+
+        assertTrue(claimExpirationTime.toInstant().isBefore(loginExpirationTime.toInstant()), "Claim Expiration after Login Expiration");
+    }
+
+    @Test
+    public void testGetBearerTokenExpirationMinimum() throws ParseException, JOSEException {
+        final long expiration = MINIMUM_DURATION_EXCEEDED.toMillis();
+        final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(IDENTITY, USERNAME, expiration, ISSUER);
+        setSignerProvider();
+
+        final String bearerToken = provider.getBearerToken(loginAuthenticationToken);
+
+        final SignedJWT signedJwt = assertTokenVerified(bearerToken);
+        final JWTClaimsSet claims = signedJwt.getJWTClaimsSet();
+        final Date claimExpirationTime = claims.getExpirationTime();
+        assertNotNull(claimExpirationTime, "Expiration Time not found");
+
+        final Date loginExpirationTime = new Date(loginAuthenticationToken.getExpiration());
+        assertNotSame(loginExpirationTime.toString(), claimExpirationTime.toString(), "Expiration Time matched");
+
+        assertTrue(claimExpirationTime.toInstant().isAfter(loginExpirationTime.toInstant()), "Claim Expiration before Login Expiration");
+    }
+
+    private void setSignerProvider() {
+        final String keyIdentifier = UUID.randomUUID().toString();
+        final JwsSignerContainer jwsSignerContainer = new JwsSignerContainer(keyIdentifier, JWS_ALGORITHM, jwsSigner);
+        when(jwsSignerProvider.getJwsSignerContainer(isA(Instant.class))).thenReturn(jwsSignerContainer);
+    }
+
+    private SignedJWT assertTokenVerified(final String bearerToken) throws ParseException, JOSEException {
+        final SignedJWT signedJwt = SignedJWT.parse(bearerToken);
+        assertTrue(signedJwt.verify(jwsVerifier), "Verification Failed");
+        return signedJwt;
+    }
 }