Skip to content

Commit 469ed09

Browse files
committed
Allow setting Clock in OAuth2TokenGenerator implementations
Closes gh-18017
1 parent 1d7f4c3 commit 469ed09

File tree

6 files changed

+89
-7
lines changed

6 files changed

+89
-7
lines changed

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.oauth2.server.authorization.token;
1818

19+
import java.time.Clock;
1920
import java.time.Instant;
2021
import java.time.temporal.ChronoUnit;
2122
import java.util.Collections;
@@ -65,6 +66,8 @@ public final class JwtGenerator implements OAuth2TokenGenerator<Jwt> {
6566

6667
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
6768

69+
private Clock clock = Clock.systemUTC();
70+
6871
/**
6972
* Constructs a {@code JwtGenerator} using the provided parameters.
7073
* @param jwtEncoder the jwt encoder
@@ -95,7 +98,7 @@ public Jwt generate(OAuth2TokenContext context) {
9598
}
9699
RegisteredClient registeredClient = context.getRegisteredClient();
97100

98-
Instant issuedAt = Instant.now();
101+
Instant issuedAt = this.clock.instant();
99102
Instant expiresAt;
100103
JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.RS256;
101104
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
@@ -208,4 +211,15 @@ public void setJwtCustomizer(OAuth2TokenCustomizer<JwtEncodingContext> jwtCustom
208211
this.jwtCustomizer = jwtCustomizer;
209212
}
210213

214+
/**
215+
* Sets the {@link Clock} used when obtaining the current instant via
216+
* {@link Clock#instant()}.
217+
* @param clock the {@link Clock} used when obtaining the current instant via
218+
* {@link Clock#instant()}
219+
*/
220+
public void setClock(Clock clock) {
221+
Assert.notNull(clock, "clock cannot be null");
222+
this.clock = clock;
223+
}
224+
211225
}

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.oauth2.server.authorization.token;
1818

19+
import java.time.Clock;
1920
import java.time.Instant;
2021
import java.util.Base64;
2122
import java.util.Collections;
@@ -56,6 +57,8 @@ public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OA
5657

5758
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
5859

60+
private Clock clock = Clock.systemUTC();
61+
5962
@Nullable
6063
@Override
6164
public OAuth2AccessToken generate(OAuth2TokenContext context) {
@@ -72,7 +75,7 @@ public OAuth2AccessToken generate(OAuth2TokenContext context) {
7275
}
7376
RegisteredClient registeredClient = context.getRegisteredClient();
7477

75-
Instant issuedAt = Instant.now();
78+
Instant issuedAt = this.clock.instant();
7679
Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive());
7780

7881
// @formatter:off
@@ -140,6 +143,17 @@ public void setAccessTokenCustomizer(OAuth2TokenCustomizer<OAuth2TokenClaimsCont
140143
this.accessTokenCustomizer = accessTokenCustomizer;
141144
}
142145

146+
/**
147+
* Sets the {@link Clock} used when obtaining the current instant via
148+
* {@link Clock#instant()}.
149+
* @param clock the {@link Clock} used when obtaining the current instant via
150+
* {@link Clock#instant()}
151+
*/
152+
public void setClock(Clock clock) {
153+
Assert.notNull(clock, "clock cannot be null");
154+
this.clock = clock;
155+
}
156+
143157
private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor {
144158

145159
private final Map<String, Object> claims;

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.oauth2.server.authorization.token;
1818

19+
import java.time.Clock;
1920
import java.time.Instant;
2021
import java.util.Base64;
2122

@@ -27,6 +28,7 @@
2728
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
2829
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
2930
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
31+
import org.springframework.util.Assert;
3032

3133
/**
3234
* An {@link OAuth2TokenGenerator} that generates an {@link OAuth2RefreshToken}.
@@ -41,6 +43,8 @@ public final class OAuth2RefreshTokenGenerator implements OAuth2TokenGenerator<O
4143
private final StringKeyGenerator refreshTokenGenerator = new Base64StringKeyGenerator(
4244
Base64.getUrlEncoder().withoutPadding(), 96);
4345

46+
private Clock clock = Clock.systemUTC();
47+
4448
@Nullable
4549
@Override
4650
public OAuth2RefreshToken generate(OAuth2TokenContext context) {
@@ -52,11 +56,22 @@ public OAuth2RefreshToken generate(OAuth2TokenContext context) {
5256
return null;
5357
}
5458

55-
Instant issuedAt = Instant.now();
59+
Instant issuedAt = this.clock.instant();
5660
Instant expiresAt = issuedAt.plus(context.getRegisteredClient().getTokenSettings().getRefreshTokenTimeToLive());
5761
return new OAuth2RefreshToken(this.refreshTokenGenerator.generateKey(), issuedAt, expiresAt);
5862
}
5963

64+
/**
65+
* Sets the {@link Clock} used when obtaining the current instant via
66+
* {@link Clock#instant()}.
67+
* @param clock the {@link Clock} used when obtaining the current instant via
68+
* {@link Clock#instant()}
69+
*/
70+
public void setClock(Clock clock) {
71+
Assert.notNull(clock, "clock cannot be null");
72+
this.clock = clock;
73+
}
74+
6075
private static boolean isPublicClientForAuthorizationCodeGrant(OAuth2TokenContext context) {
6176
// @formatter:off
6277
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType()) &&

oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/JwtGeneratorTests.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.security.oauth2.server.authorization.token;
1818

1919
import java.security.Principal;
20+
import java.time.Clock;
21+
import java.time.Duration;
2022
import java.time.Instant;
2123
import java.time.temporal.ChronoUnit;
2224
import java.util.Date;
@@ -104,6 +106,12 @@ public void setJwtCustomizerWhenNullThenThrowIllegalArgumentException() {
104106
.withMessage("jwtCustomizer cannot be null");
105107
}
106108

109+
@Test
110+
public void setClockWhenNullThenThrowIllegalArgumentException() {
111+
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> this.jwtGenerator.setClock(null))
112+
.withMessage("clock cannot be null");
113+
}
114+
107115
@Test
108116
public void generateWhenUnsupportedTokenTypeThenReturnNull() {
109117
// @formatter:off
@@ -158,7 +166,10 @@ public void generateWhenAccessTokenTypeThenReturnJwt() {
158166
.build();
159167
// @formatter:on
160168

161-
assertGeneratedTokenType(tokenContext);
169+
Clock clock = Clock.offset(Clock.systemUTC(), Duration.ofMinutes(5));
170+
this.jwtGenerator.setClock(clock);
171+
172+
assertGeneratedTokenType(tokenContext, clock);
162173
}
163174

164175
@Test
@@ -282,6 +293,10 @@ public void generateWhenIdTokenTypeWithoutSidAndRefreshTokenGrantThenReturnJwt()
282293
}
283294

284295
private void assertGeneratedTokenType(OAuth2TokenContext tokenContext) {
296+
assertGeneratedTokenType(tokenContext, Clock.systemUTC());
297+
}
298+
299+
private void assertGeneratedTokenType(OAuth2TokenContext tokenContext, Clock clock) {
285300
this.jwtGenerator.generate(tokenContext);
286301

287302
ArgumentCaptor<JwtEncodingContext> jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class);
@@ -318,7 +333,7 @@ private void assertGeneratedTokenType(OAuth2TokenContext tokenContext) {
318333
assertThat(jwtClaimsSet.getSubject()).isEqualTo(tokenContext.getAuthorization().getPrincipalName());
319334
assertThat(jwtClaimsSet.getAudience()).containsExactly(tokenContext.getRegisteredClient().getClientId());
320335

321-
Instant issuedAt = Instant.now();
336+
Instant issuedAt = clock.instant();
322337
Instant expiresAt;
323338
if (tokenContext.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
324339
expiresAt = issuedAt.plus(tokenContext.getRegisteredClient().getTokenSettings().getAccessTokenTimeToLive());

oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGeneratorTests.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.security.oauth2.server.authorization.token;
1818

1919
import java.security.Principal;
20+
import java.time.Clock;
21+
import java.time.Duration;
2022
import java.time.Instant;
2123
import java.util.Collections;
2224
import java.util.Set;
@@ -81,6 +83,13 @@ public void setAccessTokenCustomizerWhenNullThenThrowIllegalArgumentException()
8183
.withMessage("accessTokenCustomizer cannot be null");
8284
}
8385

86+
@Test
87+
public void setClockWhenNullThenThrowIllegalArgumentException() {
88+
assertThatExceptionOfType(IllegalArgumentException.class)
89+
.isThrownBy(() -> this.accessTokenGenerator.setClock(null))
90+
.withMessage("clock cannot be null");
91+
}
92+
8493
@Test
8594
public void generateWhenUnsupportedTokenTypeThenReturnNull() {
8695
// @formatter:off
@@ -150,10 +159,13 @@ public void generateWhenReferenceAccessTokenTypeThenReturnAccessToken() {
150159
.build();
151160
// @formatter:on
152161

162+
Clock clock = Clock.offset(Clock.systemUTC(), Duration.ofMinutes(5));
163+
this.accessTokenGenerator.setClock(clock);
164+
153165
OAuth2AccessToken accessToken = this.accessTokenGenerator.generate(tokenContext);
154166
assertThat(accessToken).isNotNull();
155167

156-
Instant issuedAt = Instant.now();
168+
Instant issuedAt = clock.instant();
157169
Instant expiresAt = issuedAt
158170
.plus(tokenContext.getRegisteredClient().getTokenSettings().getAccessTokenTimeToLive());
159171
assertThat(accessToken.getIssuedAt()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1));

oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGeneratorTests.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.security.oauth2.server.authorization.token;
1818

19+
import java.time.Clock;
20+
import java.time.Duration;
1921
import java.time.Instant;
2022

2123
import org.junit.jupiter.api.Test;
@@ -26,6 +28,7 @@
2628
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
2729

2830
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2932

3033
/**
3134
* Tests for {@link OAuth2RefreshTokenGenerator}.
@@ -36,6 +39,12 @@ public class OAuth2RefreshTokenGeneratorTests {
3639

3740
private final OAuth2RefreshTokenGenerator tokenGenerator = new OAuth2RefreshTokenGenerator();
3841

42+
@Test
43+
public void setClockWhenNullThenThrowIllegalArgumentException() {
44+
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> this.tokenGenerator.setClock(null))
45+
.withMessage("clock cannot be null");
46+
}
47+
3948
@Test
4049
public void generateWhenUnsupportedTokenTypeThenReturnNull() {
4150
// @formatter:off
@@ -58,10 +67,13 @@ public void generateWhenRefreshTokenTypeThenReturnRefreshToken() {
5867
.build();
5968
// @formatter:on
6069

70+
Clock clock = Clock.offset(Clock.systemUTC(), Duration.ofMinutes(5));
71+
this.tokenGenerator.setClock(clock);
72+
6173
OAuth2RefreshToken refreshToken = this.tokenGenerator.generate(tokenContext);
6274
assertThat(refreshToken).isNotNull();
6375

64-
Instant issuedAt = Instant.now();
76+
Instant issuedAt = clock.instant();
6577
Instant expiresAt = issuedAt
6678
.plus(tokenContext.getRegisteredClient().getTokenSettings().getRefreshTokenTimeToLive());
6779
assertThat(refreshToken.getIssuedAt()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1));

0 commit comments

Comments
 (0)