Skip to content

Commit 0255a24

Browse files
shanman190jgrandja
authored andcommitted
Upgrade client secret when available
Closes gh-1099
1 parent d197c18 commit 0255a24

File tree

6 files changed

+81
-12
lines changed

6 files changed

+81
-12
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ public Authentication authenticate(Authentication authentication) throws Authent
122122
throwInvalidClient("client_secret_expires_at");
123123
}
124124

125+
if (this.passwordEncoder.upgradeEncoding(registeredClient.getClientSecret())) {
126+
registeredClient = RegisteredClient.from(registeredClient)
127+
.clientSecret(this.passwordEncoder.encode(clientSecret))
128+
.build();
129+
registeredClientRepository.save(registeredClient);
130+
}
131+
125132
if (this.logger.isTraceEnabled()) {
126133
this.logger.trace("Validated client authentication parameters");
127134
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/InMemoryRegisteredClientRepository.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,14 @@ public InMemoryRegisteredClientRepository(List<RegisteredClient> registrations)
7272
@Override
7373
public void save(RegisteredClient registeredClient) {
7474
Assert.notNull(registeredClient, "registeredClient cannot be null");
75-
assertUniqueIdentifiers(registeredClient, this.idRegistrationMap);
76-
this.idRegistrationMap.put(registeredClient.getId(), registeredClient);
77-
this.clientIdRegistrationMap.put(registeredClient.getClientId(), registeredClient);
75+
if (this.idRegistrationMap.containsKey(registeredClient.getId())) {
76+
this.idRegistrationMap.put(registeredClient.getId(), registeredClient);
77+
this.clientIdRegistrationMap.put(registeredClient.getClientId(), registeredClient);
78+
} else {
79+
assertUniqueIdentifiers(registeredClient, this.idRegistrationMap);
80+
this.idRegistrationMap.put(registeredClient.getId(), registeredClient);
81+
this.clientIdRegistrationMap.put(registeredClient.getClientId(), registeredClient);
82+
}
7883
}
7984

8085
@Nullable

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepository.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
9696

9797
// @formatter:off
9898
private static final String UPDATE_REGISTERED_CLIENT_SQL = "UPDATE " + TABLE_NAME
99-
+ " SET client_name = ?, client_authentication_methods = ?, authorization_grant_types = ?,"
100-
+ " redirect_uris = ?, post_logout_redirect_uris = ?, scopes = ?, client_settings = ?, token_settings = ?"
99+
+ " SET client_secret = ?, client_secret_expires_at = ?, client_name = ?, client_authentication_methods = ?,"
100+
+ " authorization_grant_types = ?, redirect_uris = ?, post_logout_redirect_uris = ?, scopes = ?,"
101+
+ " client_settings = ?, token_settings = ?"
101102
+ " WHERE " + PK_FILTER;
102103
// @formatter:on
103104

@@ -136,8 +137,6 @@ private void updateRegisteredClient(RegisteredClient registeredClient) {
136137
SqlParameterValue id = parameters.remove(0);
137138
parameters.remove(0); // remove client_id
138139
parameters.remove(0); // remove client_id_issued_at
139-
parameters.remove(0); // remove client_secret
140-
parameters.remove(0); // remove client_secret_expires_at
141140
parameters.add(id);
142141
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
143142
this.jdbcOperations.update(UPDATE_REGISTERED_CLIENT_SQL, pss);

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ public String encode(CharSequence rawPassword) {
8585
public boolean matches(CharSequence rawPassword, String encodedPassword) {
8686
return NoOpPasswordEncoder.getInstance().matches(rawPassword, encodedPassword);
8787
}
88+
89+
@Override
90+
public boolean upgradeEncoding(String encodedPassword) {
91+
return true;
92+
}
8893
});
8994
this.authenticationProvider.setPasswordEncoder(this.passwordEncoder);
9095
}
@@ -222,6 +227,28 @@ public void authenticateWhenValidCredentialsThenAuthenticated() {
222227
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
223228
}
224229

230+
@Test
231+
public void authenticateWhenValidCredentialsAndNonExpiredThenPasswordUpgraded() {
232+
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
233+
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
234+
.thenReturn(registeredClient);
235+
236+
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
237+
registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret(), null);
238+
OAuth2ClientAuthenticationToken authenticationResult =
239+
(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
240+
241+
verify(this.passwordEncoder).matches(any(), any());
242+
verify(this.passwordEncoder).upgradeEncoding(any());
243+
verify(this.passwordEncoder).encode(any());
244+
verify(this.registeredClientRepository).save(any());
245+
assertThat(authenticationResult.isAuthenticated()).isTrue();
246+
assertThat(registeredClient).isNotSameAs(authenticationResult.getPrincipal());
247+
assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
248+
assertThat(authenticationResult.getCredentials().toString()).isEqualTo(registeredClient.getClientSecret());
249+
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
250+
}
251+
225252
@Test
226253
public void authenticateWhenAuthorizationCodeGrantAndValidCredentialsThenAuthenticated() {
227254
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/InMemoryRegisteredClientRepositoryTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,12 @@ public void saveWhenNullThenThrowIllegalArgumentException() {
153153
}
154154

155155
@Test
156-
public void saveWhenExistingIdThenThrowIllegalArgumentException() {
156+
public void saveWhenExistingIdThenUpdate() {
157157
RegisteredClient registeredClient = createRegisteredClient(
158-
this.registration.getId(), "client-id-2", "client-secret-2");
159-
assertThatIllegalArgumentException()
160-
.isThrownBy(() -> this.clients.save(registeredClient))
161-
.withMessage("Registered client must be unique. Found duplicate identifier: " + registeredClient.getId());
158+
this.registration.getId(), "client-id", "client-secret-2");
159+
this.clients.save(registeredClient);
160+
RegisteredClient savedClient = this.clients.findByClientId(registeredClient.getClientId());
161+
assertThat(savedClient.getClientSecret()).isEqualTo("client-secret-2");
162162
}
163163

164164
@Test

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
5656
import org.springframework.security.core.Authentication;
5757
import org.springframework.security.core.context.SecurityContextHolder;
58+
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
5859
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
5960
import org.springframework.security.crypto.password.PasswordEncoder;
6061
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@@ -231,6 +232,27 @@ public void requestWhenTokenRequestPostsClientCredentialsThenTokenResponse() thr
231232
verify(jwtCustomizer).customize(any());
232233
}
233234

235+
@Test
236+
public void requestWhenTokenRequestPostsClientCredentialsThenTokenResponseAndSecretUpgraded() throws Exception {
237+
this.spring.register(AuthorizationServerConfigurationCustomPasswordEncoder.class).autowire();
238+
239+
String clientSecret = "secret-2";
240+
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().clientSecret("{noop}" + clientSecret).build();
241+
this.registeredClientRepository.save(registeredClient);
242+
243+
this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
244+
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
245+
.param(OAuth2ParameterNames.SCOPE, "scope1 scope2")
246+
.param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
247+
.param(OAuth2ParameterNames.CLIENT_SECRET, clientSecret))
248+
.andExpect(status().isOk())
249+
.andExpect(jsonPath("$.access_token").isNotEmpty())
250+
.andExpect(jsonPath("$.scope").value("scope1 scope2"));
251+
252+
verify(jwtCustomizer).customize(any());
253+
assertThat(this.registeredClientRepository.findByClientId(registeredClient.getClientId()).getClientSecret()).startsWith("{bcrypt}");
254+
}
255+
234256
@Test
235257
public void requestWhenTokenEndpointCustomizedThenUsed() throws Exception {
236258
this.spring.register(AuthorizationServerConfigurationCustomTokenEndpoint.class).autowire();
@@ -429,6 +451,15 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
429451
// @formatter:on
430452
}
431453

454+
@EnableWebSecurity
455+
@Configuration(proxyBeanMethods = false)
456+
static class AuthorizationServerConfigurationCustomPasswordEncoder extends AuthorizationServerConfiguration {
457+
@Override
458+
PasswordEncoder passwordEncoder() {
459+
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
460+
}
461+
}
462+
432463
@EnableWebSecurity
433464
@Configuration(proxyBeanMethods = false)
434465
static class AuthorizationServerConfigurationCustomClientAuthentication extends AuthorizationServerConfiguration {

0 commit comments

Comments
 (0)