diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java index b548019b2..dda7c36dc 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java @@ -138,6 +138,11 @@ public static final class Token { */ public static final String ACCESS_TOKEN_TIME_TO_LIVE = TOKEN_SETTINGS_NAMESPACE.concat("access-token-time-to-live"); + /** + * Set the time-to-live for an id token. + */ + public static final String ID_TOKEN_TIME_TO_LIVE = TOKEN_SETTINGS_NAMESPACE.concat("id-token-time-to-live"); + /** * Set the {@link OAuth2TokenFormat token format} for an access token. * @since 0.2.3 diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java index 887bdca80..397e633d5 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java @@ -55,6 +55,15 @@ public Duration getAccessTokenTimeToLive() { return getSetting(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE); } + /** + * Returns the time-to-live for an id token. The default is 30 minutes. + * + * @return the time-to-live for an id token + */ + public Duration getIdTokenTimeToLive() { + return getSetting(ConfigurationSettingNames.Token.ID_TOKEN_TIME_TO_LIVE); + } + /** * Returns the token format for an access token. * The default is {@link OAuth2TokenFormat#SELF_CONTAINED}. @@ -102,6 +111,7 @@ public static Builder builder() { return new Builder() .authorizationCodeTimeToLive(Duration.ofMinutes(5)) .accessTokenTimeToLive(Duration.ofMinutes(5)) + .idTokenTimeToLive(Duration.ofMinutes(30)) .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) .reuseRefreshTokens(true) .refreshTokenTimeToLive(Duration.ofMinutes(60)) @@ -154,6 +164,18 @@ public Builder accessTokenTimeToLive(Duration accessTokenTimeToLive) { return setting(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE, accessTokenTimeToLive); } + /** + * Set the time-to-live for an id token. Must be greater than {@code Duration.ZERO}. + * + * @param idTokenTimeToLive the time-to-live for an id token + * @return the {@link Builder} for further configuration + */ + public Builder idTokenTimeToLive(Duration idTokenTimeToLive) { + Assert.notNull(idTokenTimeToLive, "idTokenTimeToLive cannot be null"); + Assert.isTrue(idTokenTimeToLive.getSeconds() > 0, "idTokenTimeToLive must be greater than Duration.ZERO"); + return setting(ConfigurationSettingNames.Token.ID_TOKEN_TIME_TO_LIVE, idTokenTimeToLive); + } + /** * Set the token format for an access token. * diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java index 3cc52de73..5d9fd0178 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java @@ -16,7 +16,6 @@ package org.springframework.security.oauth2.server.authorization.token; import java.time.Instant; -import java.time.temporal.ChronoUnit; import java.util.Collections; import org.springframework.lang.Nullable; @@ -92,8 +91,7 @@ public Jwt generate(OAuth2TokenContext context) { Instant expiresAt; JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.RS256; if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { - // TODO Allow configuration for ID Token time-to-live - expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); + expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getIdTokenTimeToLive()); if (registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm() != null) { jwsAlgorithm = registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm(); } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java index 743d73342..1e774ceec 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java @@ -16,6 +16,7 @@ package org.springframework.security.oauth2.server.authorization.settings; import java.time.Duration; +import java.util.Map; import org.junit.jupiter.api.Test; @@ -34,9 +35,10 @@ public class TokenSettingsTests { @Test public void buildWhenDefaultThenDefaultsAreSet() { TokenSettings tokenSettings = TokenSettings.builder().build(); - assertThat(tokenSettings.getSettings()).hasSize(6); + assertThat(tokenSettings.getSettings()).hasSize(7); assertThat(tokenSettings.getAuthorizationCodeTimeToLive()).isEqualTo(Duration.ofMinutes(5)); assertThat(tokenSettings.getAccessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(5)); + assertThat(tokenSettings.getIdTokenTimeToLive()).isEqualTo(Duration.ofMinutes(30)); assertThat(tokenSettings.getAccessTokenFormat()).isEqualTo(OAuth2TokenFormat.SELF_CONTAINED); assertThat(tokenSettings.isReuseRefreshTokens()).isTrue(); assertThat(tokenSettings.getRefreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60)); @@ -79,6 +81,34 @@ public void accessTokenTimeToLiveWhenProvidedThenSet() { assertThat(tokenSettings.getAccessTokenTimeToLive()).isEqualTo(accessTokenTimeToLive); } + @Test + void idTokenTimeToLiveWhenProvidedThenSet() { + var accessTokenTimeToLive = Duration.ofMinutes(15); + var tokenSettings = TokenSettings.builder() + .accessTokenTimeToLive(accessTokenTimeToLive) + .build(); + assertThat(tokenSettings.getAccessTokenTimeToLive()).isEqualTo(accessTokenTimeToLive); + } + + @Test + void idTokenTimeToLiveWhenNullOrZeroOrNegativeThenThrowIllegalArgumentException() { + var builder = TokenSettings.builder(); + assertThatThrownBy(() -> builder.idTokenTimeToLive(null)) + .isInstanceOf(IllegalArgumentException.class) + .extracting(Throwable::getMessage) + .isEqualTo("idTokenTimeToLive cannot be null"); + + assertThatThrownBy(() -> builder.idTokenTimeToLive(Duration.ZERO)) + .isInstanceOf(IllegalArgumentException.class) + .extracting(Throwable::getMessage) + .isEqualTo("idTokenTimeToLive must be greater than Duration.ZERO"); + + assertThatThrownBy(() -> builder.idTokenTimeToLive(Duration.ofSeconds(-10))) + .isInstanceOf(IllegalArgumentException.class) + .extracting(Throwable::getMessage) + .isEqualTo("idTokenTimeToLive must be greater than Duration.ZERO"); + } + @Test public void accessTokenTimeToLiveWhenNullOrZeroOrNegativeThenThrowIllegalArgumentException() { assertThatThrownBy(() -> TokenSettings.builder().accessTokenTimeToLive(null)) @@ -163,7 +193,16 @@ public void settingWhenCustomThenSet() { .setting("name1", "value1") .settings(settings -> settings.put("name2", "value2")) .build(); - assertThat(tokenSettings.getSettings()).hasSize(8); + assertThat(tokenSettings.getSettings()).hasSize(9); + assertThat(tokenSettings.getSetting("name1")).isEqualTo("value1"); + assertThat(tokenSettings.getSetting("name2")).isEqualTo("value2"); + } + + @Test + void settingWithSettings() { + var settings = Map.of("name1", "value1", "name2", "value2"); + var tokenSettings = TokenSettings.withSettings(settings).build(); + assertThat(tokenSettings.getSettings()).hasSize(2); assertThat(tokenSettings.getSetting("name1")).isEqualTo("value1"); assertThat(tokenSettings.getSetting("name2")).isEqualTo("value2"); }