From 04ff23663a177a75fa8b7ed81b76bd434aa317ff Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Sat, 12 Feb 2022 10:40:30 -0500 Subject: [PATCH] Return to a one-token-per-channel model --- .../pushy/apns/ApnsChannelFactory.java | 7 +- .../com/eatthepath/pushy/apns/ApnsClient.java | 17 +-- .../TokenAuthenticationApnsClientHandler.java | 88 ++++++----- .../auth/AuthenticationTokenProvider.java | 116 --------------- .../eatthepath/pushy/apns/ApnsClientTest.java | 5 - .../auth/AuthenticationTokenProviderTest.java | 138 ------------------ 6 files changed, 52 insertions(+), 319 deletions(-) delete mode 100644 pushy/src/main/java/com/eatthepath/pushy/apns/auth/AuthenticationTokenProvider.java delete mode 100644 pushy/src/test/java/com/eatthepath/pushy/apns/auth/AuthenticationTokenProviderTest.java diff --git a/pushy/src/main/java/com/eatthepath/pushy/apns/ApnsChannelFactory.java b/pushy/src/main/java/com/eatthepath/pushy/apns/ApnsChannelFactory.java index 18b61fecf..c41b4575b 100644 --- a/pushy/src/main/java/com/eatthepath/pushy/apns/ApnsChannelFactory.java +++ b/pushy/src/main/java/com/eatthepath/pushy/apns/ApnsChannelFactory.java @@ -22,7 +22,6 @@ package com.eatthepath.pushy.apns; -import com.eatthepath.pushy.apns.auth.AuthenticationTokenProvider; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.socket.SocketChannel; @@ -70,7 +69,6 @@ class ApnsChannelFactory implements PooledObjectFactory, Closeable { AttributeKey.valueOf(ApnsChannelFactory.class, "channelReadyPromise"); ApnsChannelFactory(final ApnsClientConfiguration clientConfiguration, - final AuthenticationTokenProvider authenticationTokenProvider, final EventLoopGroup eventLoopGroup) { this.sslContext = clientConfiguration.getSslContext(); @@ -114,9 +112,10 @@ protected void initChannel(final SocketChannel channel) { { final ApnsClientHandler.ApnsClientHandlerBuilder clientHandlerBuilder; - if (authenticationTokenProvider != null) { + if (clientConfiguration.getSigningKey().isPresent()) { clientHandlerBuilder = new TokenAuthenticationApnsClientHandler.TokenAuthenticationApnsClientHandlerBuilder() - .authenticationTokenProvider(authenticationTokenProvider) + .signingKey(clientConfiguration.getSigningKey().get()) + .tokenExpiration(clientConfiguration.getTokenExpiration()) .authority(authority) .idlePingInterval(clientConfiguration.getIdlePingInterval()); } else { diff --git a/pushy/src/main/java/com/eatthepath/pushy/apns/ApnsClient.java b/pushy/src/main/java/com/eatthepath/pushy/apns/ApnsClient.java index ace92af87..cba284ef9 100644 --- a/pushy/src/main/java/com/eatthepath/pushy/apns/ApnsClient.java +++ b/pushy/src/main/java/com/eatthepath/pushy/apns/ApnsClient.java @@ -22,7 +22,6 @@ package com.eatthepath.pushy.apns; -import com.eatthepath.pushy.apns.auth.AuthenticationTokenProvider; import com.eatthepath.pushy.apns.util.concurrent.PushNotificationFuture; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -80,8 +79,6 @@ public class ApnsClient { private final EventLoopGroup eventLoopGroup; private final boolean shouldShutDownEventLoopGroup; - private final AuthenticationTokenProvider authenticationTokenProvider; - private final ApnsChannelPool channelPool; private final ApnsClientMetricsListener metricsListener; @@ -135,15 +132,11 @@ protected ApnsClient(final ApnsClientConfiguration clientConfiguration, final Ev this.shouldShutDownEventLoopGroup = true; } - this.authenticationTokenProvider = clientConfiguration.getSigningKey() - .map(signingKey -> new AuthenticationTokenProvider(signingKey, clientConfiguration.getTokenExpiration(), this.eventLoopGroup)) - .orElse(null); - this.metricsListener = clientConfiguration.getMetricsListener() .orElseGet(NoopApnsClientMetricsListener::new); final ApnsChannelFactory channelFactory = - new ApnsChannelFactory(clientConfiguration, this.authenticationTokenProvider, this.eventLoopGroup); + new ApnsChannelFactory(clientConfiguration, this.eventLoopGroup); final ApnsChannelPoolMetricsListener channelPoolMetricsListener = new ApnsChannelPoolMetricsListener() { @@ -229,10 +222,6 @@ public PushNotificationFutureGracefully shuts down the client, closing all connections and releasing all persistent resources. The * disconnection process will wait until notifications that have been sent to the APNs server have been either @@ -257,10 +246,6 @@ public CompletableFuture close() { final CompletableFuture closeFuture; if (this.isClosed.compareAndSet(false, true)) { - if (this.authenticationTokenProvider != null) { - this.authenticationTokenProvider.close(); - } - closeFuture = new CompletableFuture<>(); this.channelPool.close().addListener((GenericFutureListener>) closePoolFuture -> { diff --git a/pushy/src/main/java/com/eatthepath/pushy/apns/TokenAuthenticationApnsClientHandler.java b/pushy/src/main/java/com/eatthepath/pushy/apns/TokenAuthenticationApnsClientHandler.java index e84fb5569..42541b015 100644 --- a/pushy/src/main/java/com/eatthepath/pushy/apns/TokenAuthenticationApnsClientHandler.java +++ b/pushy/src/main/java/com/eatthepath/pushy/apns/TokenAuthenticationApnsClientHandler.java @@ -22,25 +22,30 @@ package com.eatthepath.pushy.apns; +import com.eatthepath.pushy.apns.auth.ApnsSigningKey; import com.eatthepath.pushy.apns.auth.AuthenticationToken; -import com.eatthepath.pushy.apns.auth.AuthenticationTokenProvider; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http2.*; +import io.netty.handler.codec.http2.Http2ConnectionDecoder; +import io.netty.handler.codec.http2.Http2ConnectionEncoder; +import io.netty.handler.codec.http2.Http2Headers; +import io.netty.handler.codec.http2.Http2Settings; import io.netty.util.AsciiString; -import io.netty.util.collection.IntObjectHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; -import java.util.Map; +import java.time.Instant; import java.util.Objects; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; class TokenAuthenticationApnsClientHandler extends ApnsClientHandler { - private final AuthenticationTokenProvider authenticationTokenProvider; + private final ApnsSigningKey signingKey; + private AuthenticationToken authenticationToken; - private final Http2Connection.PropertyKey authenticationTokenPropertyKey; - private final Map unattachedAuthenticationTokensByStreamId = new IntObjectHashMap<>(); + private final Duration tokenExpiration; + private ScheduledFuture tokenExpirationFuture; private static final AsciiString APNS_AUTHORIZATION_HEADER = new AsciiString("authorization"); @@ -49,57 +54,60 @@ class TokenAuthenticationApnsClientHandler extends ApnsClientHandler { private static final Logger log = LoggerFactory.getLogger(TokenAuthenticationApnsClientHandler.class); public static class TokenAuthenticationApnsClientHandlerBuilder extends ApnsClientHandlerBuilder { - private AuthenticationTokenProvider authenticationTokenProvider; + private ApnsSigningKey signingKey; + private Duration tokenExpiration; - public TokenAuthenticationApnsClientHandlerBuilder authenticationTokenProvider(final AuthenticationTokenProvider authenticationTokenProvider) { - this.authenticationTokenProvider = authenticationTokenProvider; + public TokenAuthenticationApnsClientHandlerBuilder signingKey(final ApnsSigningKey signingKey) { + this.signingKey = signingKey; return this; } - public AuthenticationTokenProvider authenticationTokenProvider() { - return this.authenticationTokenProvider; + public ApnsSigningKey signingKey() { + return this.signingKey; + } + + public TokenAuthenticationApnsClientHandlerBuilder tokenExpiration(final Duration tokenExpiration) { + this.tokenExpiration = tokenExpiration; + return this; + } + + public Duration tokenExpiration() { + return this.tokenExpiration; } @Override public ApnsClientHandler build(final Http2ConnectionDecoder decoder, final Http2ConnectionEncoder encoder, final Http2Settings initialSettings) { Objects.requireNonNull(this.authority(), "Authority must be set before building a TokenAuthenticationApnsClientHandler."); - Objects.requireNonNull(this.authenticationTokenProvider(), "Authentication token provider must be set before building a TokenAuthenticationApnsClientHandler."); + Objects.requireNonNull(this.signingKey(), "Signing key must be set before building a TokenAuthenticationApnsClientHandler."); + Objects.requireNonNull(this.tokenExpiration(), "Token expiration duration must be set before building a TokenAuthenticationApnsClientHandler."); - final ApnsClientHandler handler = new TokenAuthenticationApnsClientHandler(decoder, encoder, initialSettings, this.authority(), this.idlePingInterval(), this.authenticationTokenProvider()); + final ApnsClientHandler handler = new TokenAuthenticationApnsClientHandler(decoder, encoder, initialSettings, this.authority(), this.idlePingInterval(), this.signingKey(), this.tokenExpiration()); this.frameListener(handler); return handler; } } - protected TokenAuthenticationApnsClientHandler(final Http2ConnectionDecoder decoder, final Http2ConnectionEncoder encoder, final Http2Settings initialSettings, final String authority, final Duration idlePingInterval, final AuthenticationTokenProvider authenticationTokenProvider) { + protected TokenAuthenticationApnsClientHandler(final Http2ConnectionDecoder decoder, final Http2ConnectionEncoder encoder, final Http2Settings initialSettings, final String authority, final Duration idlePingInterval, final ApnsSigningKey signingKey, final Duration tokenExpiration) { super(decoder, encoder, initialSettings, authority, idlePingInterval); - this.authenticationTokenProvider = Objects.requireNonNull(authenticationTokenProvider, "Authentication token provider must not be null for token-based client handlers."); - this.authenticationTokenPropertyKey = this.connection().newKey(); - } - - @Override - public void onStreamAdded(final Http2Stream stream) { - super.onStreamAdded(stream); - - stream.setProperty(this.authenticationTokenPropertyKey, this.unattachedAuthenticationTokensByStreamId.remove(stream.id())); - } - - @Override - public void onStreamRemoved(final Http2Stream stream) { - super.onStreamRemoved(stream); - - stream.removeProperty(this.authenticationTokenPropertyKey); + this.signingKey = Objects.requireNonNull(signingKey, "Signing key must not be null for token-based client handlers."); + this.tokenExpiration = Objects.requireNonNull(tokenExpiration, "Token expiration must not be null for token-based client handlers"); } @Override protected Http2Headers getHeadersForPushNotification(final ApnsPushNotification pushNotification, final ChannelHandlerContext context, final int streamId) { - final AuthenticationToken authenticationToken = this.authenticationTokenProvider.getAuthenticationToken(); - - this.unattachedAuthenticationTokensByStreamId.put(streamId, authenticationToken); + if (this.authenticationToken == null) { + log.debug("Generated a new authentication token for channel {} at stream {}", context.channel(), streamId); + this.authenticationToken = new AuthenticationToken(this.signingKey, Instant.now()); + + tokenExpirationFuture = context.executor().schedule(() -> { + log.debug("Authentication token for channel {} has expired", context.channel()); + TokenAuthenticationApnsClientHandler.this.authenticationToken = null; + }, this.tokenExpiration.toMillis(), TimeUnit.MILLISECONDS); + } return super.getHeadersForPushNotification(pushNotification, context, streamId) - .add(APNS_AUTHORIZATION_HEADER, authenticationToken.getAuthorizationHeader()); + .add(APNS_AUTHORIZATION_HEADER, this.authenticationToken.getAuthorizationHeader()); } @Override @@ -107,10 +115,7 @@ protected void handleErrorResponse(final ChannelHandlerContext context, final in super.handleErrorResponse(context, streamId, headers, pushNotification, errorResponse); if (EXPIRED_AUTH_TOKEN_REASON.equals(errorResponse.getReason())) { - log.warn("APNs server reports token for channel {} has expired.", context.channel()); - - this.authenticationTokenProvider.expireAuthenticationToken( - this.connection().stream(streamId).getProperty(this.authenticationTokenPropertyKey)); + log.warn("APNs server reports token for channel {} has expired; will close channel", context.channel()); // Once the server thinks our token has expired, it will "wedge" the connection. There's no way to recover // from this situation, and all we can do is close the connection and create a new one. @@ -120,7 +125,10 @@ protected void handleErrorResponse(final ChannelHandlerContext context, final in @Override public void channelInactive(final ChannelHandlerContext context) throws Exception { - this.unattachedAuthenticationTokensByStreamId.clear(); + if (this.tokenExpirationFuture != null) { + this.tokenExpirationFuture.cancel(false); + this.tokenExpirationFuture = null; + } super.channelInactive(context); } diff --git a/pushy/src/main/java/com/eatthepath/pushy/apns/auth/AuthenticationTokenProvider.java b/pushy/src/main/java/com/eatthepath/pushy/apns/auth/AuthenticationTokenProvider.java deleted file mode 100644 index 59407e7e3..000000000 --- a/pushy/src/main/java/com/eatthepath/pushy/apns/auth/AuthenticationTokenProvider.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2021 Jon Chambers - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.eatthepath.pushy.apns.auth; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Closeable; -import java.time.Clock; -import java.time.Duration; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -/** - * An authentication token provider provides thread-safe, non-blocking access to a shared {@link AuthenticationToken} - * and refreshes its authentication token at regular intervals. - */ -public class AuthenticationTokenProvider implements Closeable { - - private final ApnsSigningKey signingKey; - private final Clock clock; - - private final AtomicReference token; - - private final ScheduledFuture refreshTokenFuture; - - private static final Logger log = LoggerFactory.getLogger(AuthenticationTokenProvider.class); - - /** - * Constructs a new authentication token provider that will generate authentication tokens using the given signing - * key and refresh the token at the given interval. Once constructed, callers must call the constructed - * instance's {@link #close()} method to cleanly dispose of the instance. - * - * @param signingKey the signing key to use to generate authentication tokens - * @param maxTokenAge the maximum age of an authentication token before a new token will be generated - * @param scheduledExecutorService an executor service to use to schedule token refresh tasks - */ - public AuthenticationTokenProvider(final ApnsSigningKey signingKey, final Duration maxTokenAge, final ScheduledExecutorService scheduledExecutorService) { - this(signingKey, maxTokenAge, scheduledExecutorService, Clock.systemUTC()); - } - - /** - * Constructs a new authentication token provider that will generate authentication tokens using the given signing - * key and refresh the token at the given interval. Once constructed, callers must call the constructed - * instance's {@link #close()} method to cleanly dispose of the instance. - * - * @param signingKey the signing key to use to generate authentication tokens - * @param maxTokenAge the maximum age of an authentication token before a new token will be generated - * @param scheduledExecutorService an executor service to use to schedule token refresh tasks - * @param clock the clock to use to schedule tasks and manage token timestamps - */ - AuthenticationTokenProvider(final ApnsSigningKey signingKey, final Duration maxTokenAge, final ScheduledExecutorService scheduledExecutorService, final Clock clock) { - this.signingKey = signingKey; - this.clock = clock; - - this.token = new AtomicReference<>(new AuthenticationToken(signingKey, clock.instant())); - - this.refreshTokenFuture = scheduledExecutorService.scheduleAtFixedRate(this::refreshToken, maxTokenAge.toMillis(), maxTokenAge.toMillis(), TimeUnit.MILLISECONDS); - } - - void refreshToken() { - log.debug("Refreshed authentication token"); - this.token.set(new AuthenticationToken(signingKey, clock.instant())); - } - - /** - * Returns a current authentication token. Subsequent calls to this method may, but are not guaranteed to, return - * the same token because tokens are refreshed at regular intervals. This method is thread-safe and can be called by - * any number of concurrent consumers. - * - * @return a current authentication token - */ - public AuthenticationToken getAuthenticationToken() { - return this.token.get(); - } - - /** - * Marks the given authentication token as "expired," guaranteeing that this provider will not return the given - * token in subsequent calls. - * - * @param expiredToken the token to mark as expired - */ - public void expireAuthenticationToken(final AuthenticationToken expiredToken) { - this.token.compareAndSet(expiredToken, new AuthenticationToken(signingKey, clock.instant())); - } - - /** - * Shuts down this token provider, cancelling any recurring jobs to refresh tokens. Callers must call this - * method to cleanly dispose of an authentication token provider. - */ - public void close() { - this.refreshTokenFuture.cancel(false); - } -} diff --git a/pushy/src/test/java/com/eatthepath/pushy/apns/ApnsClientTest.java b/pushy/src/test/java/com/eatthepath/pushy/apns/ApnsClientTest.java index 8cb3c1bc3..b93a6a551 100644 --- a/pushy/src/test/java/com/eatthepath/pushy/apns/ApnsClientTest.java +++ b/pushy/src/test/java/com/eatthepath/pushy/apns/ApnsClientTest.java @@ -22,7 +22,6 @@ package com.eatthepath.pushy.apns; -import com.eatthepath.pushy.apns.auth.AuthenticationToken; import com.eatthepath.pushy.apns.server.*; import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification; import com.eatthepath.pushy.apns.util.concurrent.PushNotificationFuture; @@ -416,8 +415,6 @@ void testSendNotificationWithExpiredAuthenticationToken() throws Exception { final TestClientMetricsListener metricsListener = new TestClientMetricsListener(); final ApnsClient client = this.buildTokenAuthenticationClient(metricsListener); - final AuthenticationToken initialToken = client.getAuthenticationTokenProvider().getAuthenticationToken(); - try { server.start(PORT).get(); @@ -432,8 +429,6 @@ void testSendNotificationWithExpiredAuthenticationToken() throws Exception { client.close().get(); server.shutdown().get(); } - - assertNotEquals(initialToken, client.getAuthenticationTokenProvider().getAuthenticationToken()); } @ParameterizedTest diff --git a/pushy/src/test/java/com/eatthepath/pushy/apns/auth/AuthenticationTokenProviderTest.java b/pushy/src/test/java/com/eatthepath/pushy/apns/auth/AuthenticationTokenProviderTest.java deleted file mode 100644 index 72c001d91..000000000 --- a/pushy/src/test/java/com/eatthepath/pushy/apns/auth/AuthenticationTokenProviderTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2021 Jon Chambers - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.eatthepath.pushy.apns.auth; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.ECPrivateKey; -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -class AuthenticationTokenProviderTest { - - private ScheduledExecutorService scheduledExecutorService; - private SettableClock clock; - - private ApnsSigningKey signingKey; - - private AuthenticationTokenProvider authenticationTokenProvider; - - private static final String KEY_ID = "TESTKEY123"; - private static final String TEAM_ID = "TEAMID0987"; - - private static class SettableClock extends Clock { - - private Instant instant; - - public SettableClock(final Instant initialTime) { - this.instant = initialTime; - } - - public void setInstant(final Instant instant) { - this.instant = instant; - } - - @Override - public ZoneId getZone() { - return ZoneId.systemDefault(); - } - - @Override - public Clock withZone(final ZoneId zone) { - throw new UnsupportedOperationException(); - } - - @Override - public Instant instant() { - return instant; - } - } - - @BeforeEach - void setUp() throws NoSuchAlgorithmException, InvalidKeyException { - this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - this.clock = new SettableClock(Instant.now()); - - this.signingKey = new ApnsSigningKey(KEY_ID, TEAM_ID, (ECPrivateKey) KeyPairUtil.generateKeyPair().getPrivate()); - - this.authenticationTokenProvider = new AuthenticationTokenProvider(this.signingKey, Duration.ofMinutes(50), scheduledExecutorService, clock); - } - - @AfterEach - @SuppressWarnings("ResultOfMethodCallIgnored") - void tearDown() throws InterruptedException { - authenticationTokenProvider.close(); - - scheduledExecutorService.shutdown(); - scheduledExecutorService.awaitTermination(2, TimeUnit.SECONDS); - } - - @Test - void testRefreshToken() { - final Instant initialTime = clock.instant(); - final AuthenticationToken initialToken = authenticationTokenProvider.getAuthenticationToken(); - - final Instant laterTime = initialTime.plus(Duration.ofHours(1)); - clock.setInstant(laterTime); - authenticationTokenProvider.refreshToken(); - - assertNotEquals(initialToken, authenticationTokenProvider.getAuthenticationToken()); - assertEquals(laterTime, authenticationTokenProvider.getAuthenticationToken().getIssuedAt()); - } - - @Test - void getAuthenticationToken() { - final AuthenticationToken authenticationToken = authenticationTokenProvider.getAuthenticationToken(); - - assertEquals(KEY_ID, authenticationToken.getKeyId()); - assertEquals(TEAM_ID, authenticationToken.getTeamId()); - assertEquals(clock.instant(), authenticationToken.getIssuedAt()); - } - - @Test - void testExpireAuthenticationToken() { - final AuthenticationToken initialToken = authenticationTokenProvider.getAuthenticationToken(); - - clock.setInstant(clock.instant().plus(Duration.ofMinutes(1))); - - final AuthenticationToken differentToken = new AuthenticationToken(this.signingKey, initialToken.getIssuedAt().minus(Duration.ofMinutes(1))); - - authenticationTokenProvider.expireAuthenticationToken(differentToken); - assertEquals(initialToken, authenticationTokenProvider.getAuthenticationToken()); - - authenticationTokenProvider.expireAuthenticationToken(initialToken); - assertNotEquals(initialToken, authenticationTokenProvider.getAuthenticationToken()); - } -}