Skip to content

Commit 63aa5d8

Browse files
uc4w6cjgrandja
authored andcommitted
Fix client secret encoding when client dynamically registered
Closes gh-1056
1 parent b7f842f commit 63aa5d8

File tree

4 files changed

+99
-9
lines changed

4 files changed

+99
-9
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2022 the original author or authors.
2+
* Copyright 2020-2023 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.
@@ -26,6 +26,7 @@
2626
import org.springframework.security.authentication.AuthenticationProvider;
2727
import org.springframework.security.config.annotation.ObjectPostProcessor;
2828
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
29+
import org.springframework.security.crypto.password.PasswordEncoder;
2930
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3031
import org.springframework.security.oauth2.core.OAuth2Error;
3132
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
@@ -221,6 +222,10 @@ private static List<AuthenticationProvider> createDefaultAuthenticationProviders
221222
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
222223
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
223224
OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity));
225+
PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(httpSecurity, PasswordEncoder.class);
226+
if (passwordEncoder != null) {
227+
oidcClientRegistrationAuthenticationProvider.setPasswordEncoder(passwordEncoder);
228+
}
224229
authenticationProviders.add(oidcClientRegistrationAuthenticationProvider);
225230

226231
OidcClientConfigurationAuthenticationProvider oidcClientConfigurationAuthenticationProvider =

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2022 the original author or authors.
2+
* Copyright 2020-2023 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.
@@ -34,8 +34,10 @@
3434
import org.springframework.security.authentication.AuthenticationProvider;
3535
import org.springframework.security.core.Authentication;
3636
import org.springframework.security.core.AuthenticationException;
37+
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
3738
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
3839
import org.springframework.security.crypto.keygen.StringKeyGenerator;
40+
import org.springframework.security.crypto.password.PasswordEncoder;
3941
import org.springframework.security.oauth2.core.AuthorizationGrantType;
4042
import org.springframework.security.oauth2.core.ClaimAccessor;
4143
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@@ -79,6 +81,7 @@
7981
* @see OAuth2TokenGenerator
8082
* @see OidcClientRegistrationAuthenticationToken
8183
* @see OidcClientConfigurationAuthenticationProvider
84+
* @see PasswordEncoder
8285
* @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration">3. Client Registration Endpoint</a>
8386
*/
8487
public final class OidcClientRegistrationAuthenticationProvider implements AuthenticationProvider {
@@ -91,6 +94,8 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
9194
private final Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
9295
private Converter<OidcClientRegistration, RegisteredClient> registeredClientConverter;
9396

97+
private PasswordEncoder passwordEncoder;
98+
9499
/**
95100
* Constructs an {@code OidcClientRegistrationAuthenticationProvider} using the provided parameters.
96101
*
@@ -109,6 +114,21 @@ public OidcClientRegistrationAuthenticationProvider(RegisteredClientRepository r
109114
this.tokenGenerator = tokenGenerator;
110115
this.clientRegistrationConverter = new RegisteredClientOidcClientRegistrationConverter();
111116
this.registeredClientConverter = new OidcClientRegistrationRegisteredClientConverter();
117+
this.passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
118+
}
119+
120+
/**
121+
* Sets the {@link PasswordEncoder} used to encode the clientSecret
122+
* the {@link RegisteredClient#getClientSecret() client secret}.
123+
* If not set, the client secret will be encoded using
124+
* {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}.
125+
*
126+
* @param passwordEncoder the {@link PasswordEncoder} used to encode the clientSecret
127+
* @since 1.1.0
128+
*/
129+
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
130+
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
131+
this.passwordEncoder = passwordEncoder;
112132
}
113133

114134
@Override
@@ -183,7 +203,21 @@ private OidcClientRegistrationAuthenticationToken registerClient(OidcClientRegis
183203
}
184204

185205
RegisteredClient registeredClient = this.registeredClientConverter.convert(clientRegistrationAuthentication.getClientRegistration());
186-
this.registeredClientRepository.save(registeredClient);
206+
207+
// When secret exists, copy RegisteredClient and encode only secret
208+
String rawClientSecret = registeredClient.getClientSecret();
209+
String clientSecret = null;
210+
RegisteredClient saveRegisteredClient = null;
211+
if (rawClientSecret != null) {
212+
clientSecret = passwordEncoder.encode(rawClientSecret);
213+
saveRegisteredClient = RegisteredClient.from(registeredClient)
214+
.clientSecret(clientSecret)
215+
.build();
216+
} else {
217+
saveRegisteredClient = registeredClient;
218+
}
219+
220+
this.registeredClientRepository.save(saveRegisteredClient);
187221

188222
if (this.logger.isTraceEnabled()) {
189223
this.logger.trace("Saved registered client");

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

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2022 the original author or authors.
2+
* Copyright 2020-2023 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.
@@ -56,7 +56,7 @@
5656
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
5757
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
5858
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
59-
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
59+
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
6060
import org.springframework.security.crypto.password.PasswordEncoder;
6161
import org.springframework.security.oauth2.core.AuthorizationGrantType;
6262
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@@ -111,6 +111,7 @@
111111
import static org.mockito.Mockito.verify;
112112
import static org.mockito.Mockito.verifyNoInteractions;
113113
import static org.mockito.Mockito.when;
114+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
114115
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
115116
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
116117
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -289,7 +290,6 @@ public void requestWhenClientConfigurationRequestAuthorizedThenClientRegistratio
289290

290291
assertThat(clientConfigurationResponse.getClientId()).isEqualTo(clientRegistrationResponse.getClientId());
291292
assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isEqualTo(clientRegistrationResponse.getClientIdIssuedAt());
292-
assertThat(clientConfigurationResponse.getClientSecret()).isEqualTo(clientRegistrationResponse.getClientSecret());
293293
assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isEqualTo(clientRegistrationResponse.getClientSecretExpiresAt());
294294
assertThat(clientConfigurationResponse.getClientName()).isEqualTo(clientRegistrationResponse.getClientName());
295295
assertThat(clientConfigurationResponse.getRedirectUris())
@@ -357,6 +357,34 @@ public void requestWhenClientRegistrationEndpointCustomizedThenUsed() throws Exc
357357
verifyNoInteractions(authenticationFailureHandler);
358358
}
359359

360+
// gh-1056
361+
@Test
362+
public void requestWhenClientRegistersWithSecretThenClientAuthenticationSuccess() throws Exception {
363+
this.spring.register(AuthorizationServerConfiguration.class).autowire();
364+
365+
// @formatter:off
366+
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
367+
.clientName("client-name")
368+
.redirectUri("https://client.example.com")
369+
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
370+
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
371+
.scope("scope1")
372+
.scope("scope2")
373+
.build();
374+
// @formatter:on
375+
376+
OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration);
377+
378+
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
379+
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
380+
.param(OAuth2ParameterNames.SCOPE, "scope1")
381+
.with(httpBasic(clientRegistrationResponse.getClientId(), clientRegistrationResponse.getClientSecret())))
382+
.andExpect(status().isOk())
383+
.andExpect(jsonPath("$.access_token").isNotEmpty())
384+
.andExpect(jsonPath("$.scope").value("scope1"))
385+
.andReturn();
386+
}
387+
360388
@Test
361389
public void requestWhenClientRegistrationEndpointCustomizedWithAuthenticationFailureHandlerThenUsed() throws Exception {
362390
this.spring.register(CustomClientRegistrationConfiguration.class).autowire();
@@ -563,9 +591,8 @@ AuthorizationServerSettings authorizationServerSettings() {
563591

564592
@Bean
565593
PasswordEncoder passwordEncoder() {
566-
return NoOpPasswordEncoder.getInstance();
594+
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
567595
}
568596

569597
}
570-
571598
}

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2022 the original author or authors.
2+
* Copyright 2020-2023 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.
@@ -30,6 +30,8 @@
3030
import org.springframework.security.authentication.TestingAuthenticationToken;
3131
import org.springframework.security.core.Authentication;
3232
import org.springframework.security.core.authority.AuthorityUtils;
33+
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
34+
import org.springframework.security.crypto.password.PasswordEncoder;
3335
import org.springframework.security.oauth2.core.AuthorizationGrantType;
3436
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
3537
import org.springframework.security.oauth2.core.OAuth2AccessToken;
@@ -90,6 +92,8 @@ public class OidcClientRegistrationAuthenticationProviderTests {
9092
private AuthorizationServerSettings authorizationServerSettings;
9193
private OidcClientRegistrationAuthenticationProvider authenticationProvider;
9294

95+
private PasswordEncoder passwordEncoder;
96+
9397
@BeforeEach
9498
public void setUp() {
9599
this.registeredClientRepository = mock(RegisteredClientRepository.class);
@@ -106,6 +110,18 @@ public Jwt generate(OAuth2TokenContext context) {
106110
AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null));
107111
this.authenticationProvider = new OidcClientRegistrationAuthenticationProvider(
108112
this.registeredClientRepository, this.authorizationService, this.tokenGenerator);
113+
this.passwordEncoder = spy(new PasswordEncoder() {
114+
@Override
115+
public String encode(CharSequence rawPassword) {
116+
return NoOpPasswordEncoder.getInstance().encode(rawPassword);
117+
}
118+
119+
@Override
120+
public boolean matches(CharSequence rawPassword, String encodedPassword) {
121+
return NoOpPasswordEncoder.getInstance().matches(rawPassword, encodedPassword);
122+
}
123+
});
124+
this.authenticationProvider.setPasswordEncoder(this.passwordEncoder);
109125
}
110126

111127
@AfterEach
@@ -141,6 +157,13 @@ public void setRegisteredClientConverterWhenNullThenThrowIllegalArgumentExceptio
141157
.withMessage("registeredClientConverter cannot be null");
142158
}
143159

160+
@Test
161+
public void setPasswordEncoderWhenNullThenThrowIllegalArgumentException() {
162+
assertThatThrownBy(() -> this.authenticationProvider.setPasswordEncoder(null))
163+
.isInstanceOf(IllegalArgumentException.class)
164+
.hasMessage("passwordEncoder cannot be null");
165+
}
166+
144167
@Test
145168
public void supportsWhenTypeOidcClientRegistrationAuthenticationTokenThenReturnTrue() {
146169
assertThat(this.authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).isTrue();
@@ -472,6 +495,7 @@ public void authenticateWhenTokenEndpointAuthenticationSigningAlgorithmNotProvid
472495
assertThat(authenticationResult.getClientRegistration().getTokenEndpointAuthenticationSigningAlgorithm())
473496
.isEqualTo(MacAlgorithm.HS256.getName());
474497
assertThat(authenticationResult.getClientRegistration().getClientSecret()).isNotNull();
498+
verify(this.passwordEncoder).encode(any());
475499

476500
// @formatter:off
477501
builder

0 commit comments

Comments
 (0)