From c7235ec0a36d69bc53e5b7c4bbe962cd39aad45b Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Tue, 10 Mar 2026 07:22:35 -0400 Subject: [PATCH] Allow custom token settings for OAuth 2.0 dynamic client registration Closes gh-18870 --- .../OAuth2ClientRegistrationTests.java | 62 +++++++++++++++++++ ...RegistrationRegisteredClientConverter.java | 24 +++++++ 2 files changed, 86 insertions(+) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientRegistrationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientRegistrationTests.java index 801a76462d..246955bd40 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientRegistrationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientRegistrationTests.java @@ -411,6 +411,30 @@ public class OAuth2ClientRegistrationTests { .isCloseTo(expectedSecretExpiryDate, allowedDelta); } + @Test + public void requestWhenClientRegistersWithCustomTokenSettingsThenSavedToRegisteredClient() throws Exception { + this.spring.register(CustomTokenSettingsConfiguration.class).autowire(); + + // @formatter:off + OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() + .clientName("client-name") + .redirectUri("https://client.example.com") + .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) + .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) + .scope("scope1") + .scope("scope2") + .build(); + // @formatter:on + + OAuth2ClientRegistration clientRegistrationResponse = registerClient(clientRegistration); + + RegisteredClient registeredClient = this.registeredClientRepository + .findByClientId(clientRegistrationResponse.getClientId()); + + assertThat(registeredClient).isNotNull(); + assertThat(registeredClient.getTokenSettings().getAccessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60)); + } + private OAuth2ClientRegistration registerClient(OAuth2ClientRegistration clientRegistration) throws Exception { // ***** (1) Obtain the "initial" access token used for registering the client @@ -600,6 +624,44 @@ public class OAuth2ClientRegistrationTests { } + @EnableWebSecurity + @Configuration(proxyBeanMethods = false) + static class CustomTokenSettingsConfiguration extends AuthorizationServerConfiguration { + + // @formatter:off + @Bean + @Override + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + http + .oauth2AuthorizationServer((authorizationServer) -> + authorizationServer + .clientRegistrationEndpoint((clientRegistration) -> + clientRegistration + .authenticationProviders(configureClientRegistrationConverters()) + ) + ) + .authorizeHttpRequests((authorize) -> + authorize.anyRequest().authenticated() + ); + return http.build(); + } + // @formatter:on + + private Consumer> configureClientRegistrationConverters() { + // @formatter:off + return (authenticationProviders) -> + authenticationProviders.forEach((authenticationProvider) -> { + if (authenticationProvider instanceof OAuth2ClientRegistrationAuthenticationProvider provider) { + OAuth2ClientRegistrationRegisteredClientConverter clientRegistrationRegisteredClientConverter = new OAuth2ClientRegistrationRegisteredClientConverter(); + clientRegistrationRegisteredClientConverter.setTokenSettingsCustomizer((tokenSettings) -> tokenSettings.accessTokenTimeToLive(Duration.ofMinutes(60))); + provider.setRegisteredClientConverter(clientRegistrationRegisteredClientConverter); + } + }); + // @formatter:on + } + + } + @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class OpenClientRegistrationConfiguration extends AuthorizationServerConfiguration { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/OAuth2ClientRegistrationRegisteredClientConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/OAuth2ClientRegistrationRegisteredClientConverter.java index ebad12cd0f..11595e4708 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/OAuth2ClientRegistrationRegisteredClientConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/OAuth2ClientRegistrationRegisteredClientConverter.java @@ -19,6 +19,7 @@ package org.springframework.security.oauth2.server.authorization.converter; import java.time.Instant; import java.util.Base64; import java.util.UUID; +import java.util.function.Consumer; import org.springframework.core.convert.converter.Converter; import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; @@ -29,6 +30,8 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; +import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** @@ -47,6 +50,9 @@ public final class OAuth2ClientRegistrationRegisteredClientConverter private static final StringKeyGenerator CLIENT_SECRET_GENERATOR = new Base64StringKeyGenerator( Base64.getUrlEncoder().withoutPadding(), 48); + private Consumer tokenSettingsCustomizer = (tokenSettings) -> { + }; + @Override public RegisteredClient convert(OAuth2ClientRegistration clientRegistration) { // @formatter:off @@ -103,8 +109,26 @@ public final class OAuth2ClientRegistrationRegisteredClientConverter builder .clientSettings(clientSettingsBuilder.build()); + TokenSettings.Builder tokenSettingsBuilder = TokenSettings.builder(); + this.tokenSettingsCustomizer.accept(tokenSettingsBuilder); + + builder + .tokenSettings(tokenSettingsBuilder.build()); + return builder.build(); // @formatter:on } + /** + * Sets the {@code Consumer} providing access to the {@link TokenSettings.Builder} + * allowing the ability to customize the token configuration settings. + * @param tokenSettingsCustomizer the {@code Consumer} providing access to the + * {@link TokenSettings.Builder} + * @since 7.1 + */ + public void setTokenSettingsCustomizer(Consumer tokenSettingsCustomizer) { + Assert.notNull(tokenSettingsCustomizer, "tokenSettingsCustomizer cannot be null"); + this.tokenSettingsCustomizer = tokenSettingsCustomizer; + } + }