Skip to content

Commit 841001b

Browse files
Copy JWT iat from incoming token
To avoid having problems when just-expired tokens are accepted, copy the iat claim from the incoming token. Additionally, add a re-iat (re-issued at) claim containing the timestamp that the gateway has generated a replacement token
1 parent ff4bc73 commit 841001b

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

src/main/java/com/contentgrid/gateway/security/jwt/issuer/SignedJwtIssuer.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ private Mono<JWTClaimsSet> createClaims(ServerWebExchange exchange, Authenticati
101101
.filter(exp -> exp.compareTo(maxExpiration) <= 0)
102102
.orElse(maxExpiration));
103103
}))
104-
.issueTime(Objects.requireNonNullElseGet(claims.getIssueTime(), Date::new))
104+
.claim("re-iat", new Date())
105+
.issueTime(Objects.requireNonNullElseGet(claims.getIssueTime(), () -> {
106+
return findIssuedAtTime(authentication)
107+
.map(Date::from)
108+
.orElseGet(Date::new);
109+
}))
105110
.build();
106111
});
107112
}
@@ -116,6 +121,16 @@ private Optional<Instant> findExpirationTime(Authentication authentication) {
116121
return Optional.empty();
117122
}
118123

124+
private Optional<Instant> findIssuedAtTime(Authentication authentication) {
125+
var principal = authentication.getPrincipal();
126+
if(principal instanceof Jwt jwt) {
127+
return Optional.ofNullable(jwt.getIssuedAt());
128+
} else if(principal instanceof OidcUser oidcUser) {
129+
return Optional.ofNullable(oidcUser.getIdToken().getIssuedAt());
130+
}
131+
return Optional.empty();
132+
}
133+
119134
private Map<String, Object> reconstructActorChain(Actor actor) {
120135
var claims = new HashMap<>(actor.getClaims().getClaims());
121136
if(actor.getParent() != null) {

src/test/java/com/contentgrid/gateway/security/jwt/issuer/SignedJwtIssuerTest.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,15 @@ void creates_derived_jwt_for_delegation_token() {
9292
@Test
9393
void creates_derived_jwt_for_oidc_user() {
9494
var issuer = new SignedJwtIssuer(CLAIMS_SIGNER, JwtClaimsResolver.empty());
95+
96+
var iat = Instant.now().minus(3, ChronoUnit.MINUTES);
97+
var exp = Instant.now().plus(1, ChronoUnit.MINUTES);
9598
var oidcUser = new DefaultOidcUser(
9699
List.of(),
97100
OidcIdToken.withTokenValue("XXX")
98101
.subject("my-user")
102+
.issuedAt(iat)
103+
.expiresAt(exp)
99104
.build()
100105
);
101106

@@ -110,8 +115,8 @@ void creates_derived_jwt_for_oidc_user() {
110115
assertThat(issuer.issueSubstitutionToken(exchange).block()).isInstanceOfSatisfying(Jwt.class, token -> {
111116
assertThat(token.getIssuer()).hasToString("https://upstream-issuer.example");
112117
assertThat(token.getSubject()).isEqualTo("my-user");
113-
assertThat(token.getIssuedAt()).isBeforeOrEqualTo(Instant.now());
114-
assertThat(token.getExpiresAt()).isBetween(Instant.now().plus(4, ChronoUnit.MINUTES), Instant.now().plus(5, ChronoUnit.MINUTES));
118+
assertThat(token.getIssuedAt()).isCloseTo(iat, within(1, ChronoUnit.SECONDS));
119+
assertThat(token.getExpiresAt()).isCloseTo(exp, within(1, ChronoUnit.SECONDS));
115120
assertThat(token.getTokenValue()).satisfies(verifyJwtSignedBy(issuer));
116121
});
117122
}
@@ -179,6 +184,33 @@ void new_jwt_with_expiry_shorter_than_max() {
179184
});
180185
}
181186

187+
@Test
188+
void derived_jwt_for_just_expired_jwt() {
189+
var issuer = new SignedJwtIssuer(CLAIMS_SIGNER, JwtClaimsResolver.empty());
190+
191+
var iat = Instant.now().minus(5, ChronoUnit.MINUTES);
192+
var expiry = Instant.now().minus(10, ChronoUnit.SECONDS);
193+
194+
var exchange = createExchange(
195+
new JwtAuthenticationToken(Jwt.withTokenValue("XXXX")
196+
.header("alg", "RS256")
197+
.issuedAt(iat)
198+
.expiresAt(expiry)
199+
.build(),
200+
List.of(new PrincipalAuthenticationDetailsGrantedAuthority(new Actor(
201+
ActorType.USER,
202+
() -> Map.of("iss", "https://upstream-issuer.example", "sub", "my-user"),
203+
null
204+
)))
205+
)
206+
);
207+
208+
assertThat(issuer.issueSubstitutionToken(exchange).block()).isInstanceOfSatisfying(Jwt.class, token -> {
209+
assertThat(token.getIssuedAt()).isCloseTo(iat, within(1, ChronoUnit.SECONDS));
210+
assertThat(token.getExpiresAt()).isCloseTo(expiry, within(1, ChronoUnit.SECONDS));
211+
});
212+
}
213+
182214
static ServerWebExchange createExchange(Authentication authentication) {
183215
var request = MockServerHttpRequest.get("/").build();
184216
var securityContext = new SecurityContextImpl(authentication);

0 commit comments

Comments
 (0)