Skip to content

Commit dffe22a

Browse files
committed
Merge branch '1.5.x'
2 parents 7ad0c31 + 6052cab commit dffe22a

File tree

2 files changed

+107
-2
lines changed

2 files changed

+107
-2
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2023 the original author or authors.
2+
* Copyright 2020-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,6 +49,10 @@ public OidcClientRegistration convert(RegisteredClient registeredClient) {
4949
builder.clientSecret(registeredClient.getClientSecret());
5050
}
5151

52+
if (registeredClient.getClientSecretExpiresAt() != null) {
53+
builder.clientSecretExpiresAt(registeredClient.getClientSecretExpiresAt());
54+
}
55+
5256
builder.redirectUris((redirectUris) ->
5357
redirectUris.addAll(registeredClient.getRedirectUris()));
5458

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2024 the original author or authors.
2+
* Copyright 2020-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
1717

18+
import java.time.Duration;
1819
import java.time.Instant;
1920
import java.time.temporal.ChronoUnit;
2021
import java.util.Collections;
@@ -31,6 +32,7 @@
3132
import jakarta.servlet.http.HttpServletResponse;
3233
import okhttp3.mockwebserver.MockResponse;
3334
import okhttp3.mockwebserver.MockWebServer;
35+
import org.assertj.core.data.TemporalUnitWithinOffset;
3436
import org.junit.jupiter.api.AfterAll;
3537
import org.junit.jupiter.api.AfterEach;
3638
import org.junit.jupiter.api.BeforeAll;
@@ -508,6 +510,40 @@ public void requestWhenClientRegistersWithCustomMetadataThenSavedToRegisteredCli
508510
assertThat(registeredClient.getClientSettings().<String>getSetting("non-registered-custom-metadata")).isNull();
509511
}
510512

513+
// gh-2111
514+
@Test
515+
public void requestWhenClientRegistersWithSecretExpirationThenClientRegistrationResponse() throws Exception {
516+
this.spring.register(ClientSecretExpirationConfiguration.class).autowire();
517+
518+
// @formatter:off
519+
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
520+
.clientName("client-name")
521+
.redirectUri("https://client.example.com")
522+
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
523+
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
524+
.scope("scope1")
525+
.scope("scope2")
526+
.build();
527+
// @formatter:on
528+
529+
OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration);
530+
531+
Instant expectedSecretExpiryDate = Instant.now().plus(Duration.ofHours(24));
532+
TemporalUnitWithinOffset allowedDelta = new TemporalUnitWithinOffset(1, ChronoUnit.MINUTES);
533+
534+
// Returned response contains expiration date
535+
assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNotNull()
536+
.isCloseTo(expectedSecretExpiryDate, allowedDelta);
537+
538+
RegisteredClient registeredClient = this.registeredClientRepository
539+
.findByClientId(clientRegistrationResponse.getClientId());
540+
541+
// Persisted RegisteredClient contains expiration date
542+
assertThat(registeredClient).isNotNull();
543+
assertThat(registeredClient.getClientSecretExpiresAt()).isNotNull()
544+
.isCloseTo(expectedSecretExpiryDate, allowedDelta);
545+
}
546+
511547
private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
512548
// ***** (1) Obtain the "initial" access token used for registering the client
513549

@@ -685,6 +721,48 @@ private Consumer<List<AuthenticationProvider>> configureClientRegistrationConver
685721

686722
}
687723

724+
@EnableWebSecurity
725+
@Configuration(proxyBeanMethods = false)
726+
static class ClientSecretExpirationConfiguration extends AuthorizationServerConfiguration {
727+
728+
// @formatter:off
729+
@Bean
730+
@Override
731+
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
732+
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
733+
OAuth2AuthorizationServerConfigurer.authorizationServer();
734+
http
735+
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
736+
.with(authorizationServerConfigurer, (authorizationServer) ->
737+
authorizationServer
738+
.oidc((oidc) ->
739+
oidc
740+
.clientRegistrationEndpoint((clientRegistration) ->
741+
clientRegistration
742+
.authenticationProviders(configureClientRegistrationConverters())
743+
)
744+
)
745+
)
746+
.authorizeHttpRequests((authorize) ->
747+
authorize.anyRequest().authenticated()
748+
);
749+
return http.build();
750+
}
751+
// @formatter:on
752+
753+
private Consumer<List<AuthenticationProvider>> configureClientRegistrationConverters() {
754+
// @formatter:off
755+
return (authenticationProviders) ->
756+
authenticationProviders.forEach((authenticationProvider) -> {
757+
if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) {
758+
provider.setRegisteredClientConverter(new ClientSecretExpirationRegisteredClientConverter());
759+
}
760+
});
761+
// @formatter:on
762+
}
763+
764+
}
765+
688766
@EnableWebSecurity
689767
@Configuration(proxyBeanMethods = false)
690768
static class AuthorizationServerConfiguration {
@@ -814,4 +892,27 @@ public OidcClientRegistration convert(RegisteredClient registeredClient) {
814892

815893
}
816894

895+
/**
896+
* This customization adds client secret expiration time by setting
897+
* {@code RegisteredClient.clientSecretExpiresAt} during
898+
* {@code OidcClientRegistration} -> {@code RegisteredClient} conversion
899+
*/
900+
private static final class ClientSecretExpirationRegisteredClientConverter
901+
implements Converter<OidcClientRegistration, RegisteredClient> {
902+
903+
private static final OidcClientRegistrationRegisteredClientConverter delegate = new OidcClientRegistrationRegisteredClientConverter();
904+
905+
@Override
906+
public RegisteredClient convert(OidcClientRegistration clientRegistration) {
907+
RegisteredClient registeredClient = delegate.convert(clientRegistration);
908+
RegisteredClient.Builder registeredClientBuilder = RegisteredClient.from(registeredClient);
909+
910+
Instant clientSecretExpiresAt = Instant.now().plus(Duration.ofHours(24));
911+
registeredClientBuilder.clientSecretExpiresAt(clientSecretExpiresAt);
912+
913+
return registeredClientBuilder.build();
914+
}
915+
916+
}
917+
817918
}

0 commit comments

Comments
 (0)