Skip to content

Commit 7c9d634

Browse files
Merge pull request #289 from xenit-eu/ACC-1632
Fix HTTP 500 for a just-expired token
2 parents c314aa9 + 841001b commit 7c9d634

File tree

3 files changed

+62
-6
lines changed

3 files changed

+62
-6
lines changed

build.gradle

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import org.springframework.boot.gradle.tasks.run.BootRun
2+
13
plugins {
24
id 'java'
35
id 'java-test-fixtures'
@@ -23,7 +25,14 @@ java {
2325
}
2426
}
2527

26-
tasks.register("keycloakBootRun", org.springframework.boot.gradle.tasks.run.BootRun.class) {
28+
// Configure all bootRun tasks to use the toolchain from above
29+
tasks.withType(BootRun.class).configureEach {
30+
var toolchain = project.extensions.getByType(JavaPluginExtension.class).toolchain;
31+
var toolchainService = project.extensions.getByType(JavaToolchainService.class);
32+
it.javaLauncher.convention(toolchainService.launcherFor(toolchain))
33+
}
34+
35+
tasks.register("keycloakBootRun", BootRun.class) {
2736
description = "Runs the Spring Boot application with the Keycloak profile"
2837
group = ApplicationPlugin.APPLICATION_GROUP
2938
classpath = tasks.bootRun.classpath
@@ -32,7 +41,7 @@ tasks.register("keycloakBootRun", org.springframework.boot.gradle.tasks.run.Boo
3241
systemProperty("spring.profiles.active", "bootRun,keycloak")
3342
}
3443

35-
tasks.register("consoleBootRun", org.springframework.boot.gradle.tasks.run.BootRun.class) {
44+
tasks.register("consoleBootRun", BootRun.class) {
3645
description = "Runs the Spring Boot application with routing config for ContentGrid Console development"
3746
group = ApplicationPlugin.APPLICATION_GROUP
3847
classpath = tasks.bootRun.classpath
@@ -41,7 +50,7 @@ tasks.register("consoleBootRun", org.springframework.boot.gradle.tasks.run.Boot
4150
systemProperty("spring.profiles.active", "bootRun,console")
4251
}
4352

44-
tasks.register("runtimeBootRun", org.springframework.boot.gradle.tasks.run.BootRun.class) {
53+
tasks.register("runtimeBootRun", BootRun.class) {
4554
description = "Runs ContentGrid Gateway with config for ContentGrid Runtime Platform"
4655
group = ApplicationPlugin.APPLICATION_GROUP
4756
classpath = tasks.bootRun.classpath

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)