Skip to content

Commit

Permalink
Merge pull request #289 from xenit-eu/ACC-1632
Browse files Browse the repository at this point in the history
Fix HTTP 500 for a just-expired token
  • Loading branch information
vierbergenlars authored Oct 7, 2024
2 parents c314aa9 + 841001b commit 7c9d634
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 6 deletions.
15 changes: 12 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.springframework.boot.gradle.tasks.run.BootRun

plugins {
id 'java'
id 'java-test-fixtures'
Expand All @@ -23,7 +25,14 @@ java {
}
}

tasks.register("keycloakBootRun", org.springframework.boot.gradle.tasks.run.BootRun.class) {
// Configure all bootRun tasks to use the toolchain from above
tasks.withType(BootRun.class).configureEach {
var toolchain = project.extensions.getByType(JavaPluginExtension.class).toolchain;
var toolchainService = project.extensions.getByType(JavaToolchainService.class);
it.javaLauncher.convention(toolchainService.launcherFor(toolchain))
}

tasks.register("keycloakBootRun", BootRun.class) {
description = "Runs the Spring Boot application with the Keycloak profile"
group = ApplicationPlugin.APPLICATION_GROUP
classpath = tasks.bootRun.classpath
Expand All @@ -32,7 +41,7 @@ tasks.register("keycloakBootRun", org.springframework.boot.gradle.tasks.run.Boo
systemProperty("spring.profiles.active", "bootRun,keycloak")
}

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

tasks.register("runtimeBootRun", org.springframework.boot.gradle.tasks.run.BootRun.class) {
tasks.register("runtimeBootRun", BootRun.class) {
description = "Runs ContentGrid Gateway with config for ContentGrid Runtime Platform"
group = ApplicationPlugin.APPLICATION_GROUP
classpath = tasks.bootRun.classpath
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,12 @@ private Mono<JWTClaimsSet> createClaims(ServerWebExchange exchange, Authenticati
.filter(exp -> exp.compareTo(maxExpiration) <= 0)
.orElse(maxExpiration));
}))
.issueTime(Objects.requireNonNullElseGet(claims.getIssueTime(), Date::new))
.claim("re-iat", new Date())
.issueTime(Objects.requireNonNullElseGet(claims.getIssueTime(), () -> {
return findIssuedAtTime(authentication)
.map(Date::from)
.orElseGet(Date::new);
}))
.build();
});
}
Expand All @@ -116,6 +121,16 @@ private Optional<Instant> findExpirationTime(Authentication authentication) {
return Optional.empty();
}

private Optional<Instant> findIssuedAtTime(Authentication authentication) {
var principal = authentication.getPrincipal();
if(principal instanceof Jwt jwt) {
return Optional.ofNullable(jwt.getIssuedAt());
} else if(principal instanceof OidcUser oidcUser) {
return Optional.ofNullable(oidcUser.getIdToken().getIssuedAt());
}
return Optional.empty();
}

private Map<String, Object> reconstructActorChain(Actor actor) {
var claims = new HashMap<>(actor.getClaims().getClaims());
if(actor.getParent() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,15 @@ void creates_derived_jwt_for_delegation_token() {
@Test
void creates_derived_jwt_for_oidc_user() {
var issuer = new SignedJwtIssuer(CLAIMS_SIGNER, JwtClaimsResolver.empty());

var iat = Instant.now().minus(3, ChronoUnit.MINUTES);
var exp = Instant.now().plus(1, ChronoUnit.MINUTES);
var oidcUser = new DefaultOidcUser(
List.of(),
OidcIdToken.withTokenValue("XXX")
.subject("my-user")
.issuedAt(iat)
.expiresAt(exp)
.build()
);

Expand All @@ -110,8 +115,8 @@ void creates_derived_jwt_for_oidc_user() {
assertThat(issuer.issueSubstitutionToken(exchange).block()).isInstanceOfSatisfying(Jwt.class, token -> {
assertThat(token.getIssuer()).hasToString("https://upstream-issuer.example");
assertThat(token.getSubject()).isEqualTo("my-user");
assertThat(token.getIssuedAt()).isBeforeOrEqualTo(Instant.now());
assertThat(token.getExpiresAt()).isBetween(Instant.now().plus(4, ChronoUnit.MINUTES), Instant.now().plus(5, ChronoUnit.MINUTES));
assertThat(token.getIssuedAt()).isCloseTo(iat, within(1, ChronoUnit.SECONDS));
assertThat(token.getExpiresAt()).isCloseTo(exp, within(1, ChronoUnit.SECONDS));
assertThat(token.getTokenValue()).satisfies(verifyJwtSignedBy(issuer));
});
}
Expand Down Expand Up @@ -179,6 +184,33 @@ void new_jwt_with_expiry_shorter_than_max() {
});
}

@Test
void derived_jwt_for_just_expired_jwt() {
var issuer = new SignedJwtIssuer(CLAIMS_SIGNER, JwtClaimsResolver.empty());

var iat = Instant.now().minus(5, ChronoUnit.MINUTES);
var expiry = Instant.now().minus(10, ChronoUnit.SECONDS);

var exchange = createExchange(
new JwtAuthenticationToken(Jwt.withTokenValue("XXXX")
.header("alg", "RS256")
.issuedAt(iat)
.expiresAt(expiry)
.build(),
List.of(new PrincipalAuthenticationDetailsGrantedAuthority(new Actor(
ActorType.USER,
() -> Map.of("iss", "https://upstream-issuer.example", "sub", "my-user"),
null
)))
)
);

assertThat(issuer.issueSubstitutionToken(exchange).block()).isInstanceOfSatisfying(Jwt.class, token -> {
assertThat(token.getIssuedAt()).isCloseTo(iat, within(1, ChronoUnit.SECONDS));
assertThat(token.getExpiresAt()).isCloseTo(expiry, within(1, ChronoUnit.SECONDS));
});
}

static ServerWebExchange createExchange(Authentication authentication) {
var request = MockServerHttpRequest.get("/").build();
var securityContext = new SecurityContextImpl(authentication);
Expand Down

0 comments on commit 7c9d634

Please sign in to comment.