Browse Source

Fix to return client_secret_expires_at in client registration response

Issue gh-2111

Closes gh-2134

Signed-off-by: wheleph <wheleph@gmail.com>
pull/2193/head
wheleph 5 months ago committed by Joe Grandja
parent
commit
a94096b03c
  1. 4
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java
  2. 105
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java

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

@ -49,6 +49,10 @@ public final class RegisteredClientOidcClientRegistrationConverter @@ -49,6 +49,10 @@ public final class RegisteredClientOidcClientRegistrationConverter
builder.clientSecret(registeredClient.getClientSecret());
}
if (registeredClient.getClientSecretExpiresAt() != null) {
builder.clientSecretExpiresAt(registeredClient.getClientSecretExpiresAt());
}
builder.redirectUris((redirectUris) ->
redirectUris.addAll(registeredClient.getRedirectUris()));

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

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
@ -31,6 +32,7 @@ import com.nimbusds.jose.proc.SecurityContext; @@ -31,6 +32,7 @@ import com.nimbusds.jose.proc.SecurityContext;
import jakarta.servlet.http.HttpServletResponse;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.assertj.core.data.TemporalUnitWithinOffset;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@ -508,6 +510,46 @@ public class OidcClientRegistrationTests { @@ -508,6 +510,46 @@ public class OidcClientRegistrationTests {
assertThat(registeredClient.getClientSettings().<String>getSetting("non-registered-custom-metadata")).isNull();
}
/**
* Scenario to validate that if there's a customization that sets client secret expiration date, then the date
* is persisted and returned in the registration response
*/
@Test
public void requestWhenClientRegistersWithSecretExpirationThenClientRegistrationResponse() throws Exception {
this.spring.register(ClientSecretExpirationConfiguration.class).autowire();
// @formatter:off
OidcClientRegistration clientRegistration = OidcClientRegistration.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
OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration);
Instant expectedSecretExpiryDate = Instant.now().plus(Duration.ofHours(24));
TemporalUnitWithinOffset allowedDelta = new TemporalUnitWithinOffset(1, ChronoUnit.MINUTES);
// Returned response contains expiration date
assertThat(clientRegistrationResponse.getClientSecretExpiresAt())
.isNotNull()
.isCloseTo(expectedSecretExpiryDate, allowedDelta);
RegisteredClient registeredClient = this.registeredClientRepository
.findByClientId(clientRegistrationResponse.getClientId());
// Persisted RegisteredClient contains expiration date
assertThat(registeredClient)
.isNotNull();
assertThat(registeredClient.getClientSecretExpiresAt())
.isNotNull()
.isCloseTo(expectedSecretExpiryDate, allowedDelta);
}
private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
// ***** (1) Obtain the "initial" access token used for registering the client
@ -685,6 +727,48 @@ public class OidcClientRegistrationTests { @@ -685,6 +727,48 @@ public class OidcClientRegistrationTests {
}
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class ClientSecretExpirationConfiguration extends AuthorizationServerConfiguration {
// @formatter:off
@Bean
@Override
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
OAuth2AuthorizationServerConfigurer.authorizationServer();
http
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer
.oidc((oidc) ->
oidc
.clientRegistrationEndpoint((clientRegistration) ->
clientRegistration
.authenticationProviders(configureClientRegistrationConverters())
)
)
)
.authorizeHttpRequests((authorize) ->
authorize.anyRequest().authenticated()
);
return http.build();
}
// @formatter:on
private Consumer<List<AuthenticationProvider>> configureClientRegistrationConverters() {
// @formatter:off
return (authenticationProviders) ->
authenticationProviders.forEach((authenticationProvider) -> {
if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) {
provider.setRegisteredClientConverter(new ClientSecretExpirationRegisteredClientConverter());
}
});
// @formatter:on
}
}
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
static class AuthorizationServerConfiguration {
@ -814,4 +898,25 @@ public class OidcClientRegistrationTests { @@ -814,4 +898,25 @@ public class OidcClientRegistrationTests {
}
/**
* This customization adds client secret expiration time by setting {@code RegisteredClient.clientSecretExpiresAt}
* during {@code OidcClientRegistration} -> {@code RegisteredClient} conversion
*/
private static final class ClientSecretExpirationRegisteredClientConverter
implements Converter<OidcClientRegistration, RegisteredClient> {
private static final OidcClientRegistrationRegisteredClientConverter delegate =
new OidcClientRegistrationRegisteredClientConverter();
@Override
public RegisteredClient convert(OidcClientRegistration clientRegistration) {
RegisteredClient registeredClient = delegate.convert(clientRegistration);
var registeredClientBuilder = RegisteredClient.from(registeredClient);
var clientSecretExpiresAt = Instant.now().plus(Duration.ofHours(24));
registeredClientBuilder.clientSecretExpiresAt(clientSecretExpiresAt);
return registeredClientBuilder.build();
}
}
}

Loading…
Cancel
Save