Merge branch '1.0.x'
This commit is contained in:
@@ -30,12 +30,15 @@ import java.util.UUID;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
@@ -79,6 +82,7 @@ import org.springframework.util.StringUtils;
|
|||||||
* @see OAuth2TokenGenerator
|
* @see OAuth2TokenGenerator
|
||||||
* @see OidcClientRegistrationAuthenticationToken
|
* @see OidcClientRegistrationAuthenticationToken
|
||||||
* @see OidcClientConfigurationAuthenticationProvider
|
* @see OidcClientConfigurationAuthenticationProvider
|
||||||
|
* @see PasswordEncoder
|
||||||
* @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration">3. Client Registration Endpoint</a>
|
* @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration">3. Client Registration Endpoint</a>
|
||||||
*/
|
*/
|
||||||
public final class OidcClientRegistrationAuthenticationProvider implements AuthenticationProvider {
|
public final class OidcClientRegistrationAuthenticationProvider implements AuthenticationProvider {
|
||||||
@@ -90,6 +94,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
|
|||||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||||
private final Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
|
private final Converter<RegisteredClient, OidcClientRegistration> clientRegistrationConverter;
|
||||||
private Converter<OidcClientRegistration, RegisteredClient> registeredClientConverter;
|
private Converter<OidcClientRegistration, RegisteredClient> registeredClientConverter;
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an {@code OidcClientRegistrationAuthenticationProvider} using the provided parameters.
|
* Constructs an {@code OidcClientRegistrationAuthenticationProvider} using the provided parameters.
|
||||||
@@ -109,6 +114,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
|
|||||||
this.tokenGenerator = tokenGenerator;
|
this.tokenGenerator = tokenGenerator;
|
||||||
this.clientRegistrationConverter = new RegisteredClientOidcClientRegistrationConverter();
|
this.clientRegistrationConverter = new RegisteredClientOidcClientRegistrationConverter();
|
||||||
this.registeredClientConverter = new OidcClientRegistrationRegisteredClientConverter();
|
this.registeredClientConverter = new OidcClientRegistrationRegisteredClientConverter();
|
||||||
|
this.passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -167,6 +173,13 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
|
|||||||
this.registeredClientConverter = registeredClientConverter;
|
this.registeredClientConverter = registeredClientConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-1056
|
||||||
|
@Autowired(required = false)
|
||||||
|
void setPasswordEncoder(PasswordEncoder passwordEncoder) {
|
||||||
|
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
private OidcClientRegistrationAuthenticationToken registerClient(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
|
private OidcClientRegistrationAuthenticationToken registerClient(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
|
||||||
OAuth2Authorization authorization) {
|
OAuth2Authorization authorization) {
|
||||||
|
|
||||||
@@ -187,7 +200,16 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
|
|||||||
}
|
}
|
||||||
|
|
||||||
RegisteredClient registeredClient = this.registeredClientConverter.convert(clientRegistrationAuthentication.getClientRegistration());
|
RegisteredClient registeredClient = this.registeredClientConverter.convert(clientRegistrationAuthentication.getClientRegistration());
|
||||||
this.registeredClientRepository.save(registeredClient);
|
|
||||||
|
if (StringUtils.hasText(registeredClient.getClientSecret())) {
|
||||||
|
// Encode the client secret
|
||||||
|
RegisteredClient updatedRegisteredClient = RegisteredClient.from(registeredClient)
|
||||||
|
.clientSecret(this.passwordEncoder.encode(registeredClient.getClientSecret()))
|
||||||
|
.build();
|
||||||
|
this.registeredClientRepository.save(updatedRegisteredClient);
|
||||||
|
} else {
|
||||||
|
this.registeredClientRepository.save(registeredClient);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.logger.isTraceEnabled()) {
|
if (this.logger.isTraceEnabled()) {
|
||||||
this.logger.trace("Saved registered client");
|
this.logger.trace("Saved registered client");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020-2022 the original author or authors.
|
* Copyright 2020-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -57,7 +57,7 @@ import org.springframework.security.config.Customizer;
|
|||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
@@ -112,6 +112,7 @@ import static org.mockito.Mockito.reset;
|
|||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoInteractions;
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
|
||||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
@@ -290,7 +291,7 @@ public class OidcClientRegistrationTests {
|
|||||||
|
|
||||||
assertThat(clientConfigurationResponse.getClientId()).isEqualTo(clientRegistrationResponse.getClientId());
|
assertThat(clientConfigurationResponse.getClientId()).isEqualTo(clientRegistrationResponse.getClientId());
|
||||||
assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isEqualTo(clientRegistrationResponse.getClientIdIssuedAt());
|
assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isEqualTo(clientRegistrationResponse.getClientIdIssuedAt());
|
||||||
assertThat(clientConfigurationResponse.getClientSecret()).isEqualTo(clientRegistrationResponse.getClientSecret());
|
assertThat(clientConfigurationResponse.getClientSecret()).isNotNull();
|
||||||
assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isEqualTo(clientRegistrationResponse.getClientSecretExpiresAt());
|
assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isEqualTo(clientRegistrationResponse.getClientSecretExpiresAt());
|
||||||
assertThat(clientConfigurationResponse.getClientName()).isEqualTo(clientRegistrationResponse.getClientName());
|
assertThat(clientConfigurationResponse.getClientName()).isEqualTo(clientRegistrationResponse.getClientName());
|
||||||
assertThat(clientConfigurationResponse.getRedirectUris())
|
assertThat(clientConfigurationResponse.getRedirectUris())
|
||||||
@@ -371,6 +372,34 @@ public class OidcClientRegistrationTests {
|
|||||||
verifyNoInteractions(authenticationSuccessHandler);
|
verifyNoInteractions(authenticationSuccessHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-1056
|
||||||
|
@Test
|
||||||
|
public void requestWhenClientRegistersWithSecretThenClientAuthenticationSuccess() throws Exception {
|
||||||
|
this.spring.register(AuthorizationServerConfiguration.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);
|
||||||
|
|
||||||
|
this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
|
||||||
|
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||||
|
.param(OAuth2ParameterNames.SCOPE, "scope1")
|
||||||
|
.with(httpBasic(clientRegistrationResponse.getClientId(), clientRegistrationResponse.getClientSecret())))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.scope").value("scope1"))
|
||||||
|
.andReturn();
|
||||||
|
}
|
||||||
|
|
||||||
private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
|
private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
|
||||||
// ***** (1) Obtain the "initial" access token used for registering the client
|
// ***** (1) Obtain the "initial" access token used for registering the client
|
||||||
|
|
||||||
@@ -566,7 +595,7 @@ public class OidcClientRegistrationTests {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
PasswordEncoder passwordEncoder() {
|
PasswordEncoder passwordEncoder() {
|
||||||
return NoOpPasswordEncoder.getInstance();
|
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import org.mockito.ArgumentCaptor;
|
|||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
@@ -71,9 +73,11 @@ import static org.mockito.ArgumentMatchers.any;
|
|||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,6 +91,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
|||||||
private OAuth2AuthorizationService authorizationService;
|
private OAuth2AuthorizationService authorizationService;
|
||||||
private JwtEncoder jwtEncoder;
|
private JwtEncoder jwtEncoder;
|
||||||
private OAuth2TokenGenerator<?> tokenGenerator;
|
private OAuth2TokenGenerator<?> tokenGenerator;
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
private AuthorizationServerSettings authorizationServerSettings;
|
private AuthorizationServerSettings authorizationServerSettings;
|
||||||
private OidcClientRegistrationAuthenticationProvider authenticationProvider;
|
private OidcClientRegistrationAuthenticationProvider authenticationProvider;
|
||||||
|
|
||||||
@@ -102,10 +107,22 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
|||||||
return jwtGenerator.generate(context);
|
return jwtGenerator.generate(context);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.passwordEncoder = spy(new PasswordEncoder() {
|
||||||
|
@Override
|
||||||
|
public String encode(CharSequence rawPassword) {
|
||||||
|
return NoOpPasswordEncoder.getInstance().encode(rawPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||||
|
return NoOpPasswordEncoder.getInstance().matches(rawPassword, encodedPassword);
|
||||||
|
}
|
||||||
|
});
|
||||||
this.authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build();
|
this.authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build();
|
||||||
AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null));
|
AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null));
|
||||||
this.authenticationProvider = new OidcClientRegistrationAuthenticationProvider(
|
this.authenticationProvider = new OidcClientRegistrationAuthenticationProvider(
|
||||||
this.registeredClientRepository, this.authorizationService, this.tokenGenerator);
|
this.registeredClientRepository, this.authorizationService, this.tokenGenerator);
|
||||||
|
this.authenticationProvider.setPasswordEncoder(this.passwordEncoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
@@ -141,6 +158,13 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
|||||||
.withMessage("registeredClientConverter cannot be null");
|
.withMessage("registeredClientConverter cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setPasswordEncoderWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> this.authenticationProvider.setPasswordEncoder(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("passwordEncoder cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void supportsWhenTypeOidcClientRegistrationAuthenticationTokenThenReturnTrue() {
|
public void supportsWhenTypeOidcClientRegistrationAuthenticationTokenThenReturnTrue() {
|
||||||
assertThat(this.authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).isTrue();
|
assertThat(this.authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).isTrue();
|
||||||
@@ -544,6 +568,8 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
|||||||
assertThat(authenticationResult.getClientRegistration().getTokenEndpointAuthenticationSigningAlgorithm())
|
assertThat(authenticationResult.getClientRegistration().getTokenEndpointAuthenticationSigningAlgorithm())
|
||||||
.isEqualTo(MacAlgorithm.HS256.getName());
|
.isEqualTo(MacAlgorithm.HS256.getName());
|
||||||
assertThat(authenticationResult.getClientRegistration().getClientSecret()).isNotNull();
|
assertThat(authenticationResult.getClientRegistration().getClientSecret()).isNotNull();
|
||||||
|
verify(this.passwordEncoder).encode(any());
|
||||||
|
reset(this.passwordEncoder);
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
builder
|
builder
|
||||||
@@ -555,6 +581,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
|||||||
assertThat(authenticationResult.getClientRegistration().getTokenEndpointAuthenticationSigningAlgorithm())
|
assertThat(authenticationResult.getClientRegistration().getTokenEndpointAuthenticationSigningAlgorithm())
|
||||||
.isEqualTo(SignatureAlgorithm.RS256.getName());
|
.isEqualTo(SignatureAlgorithm.RS256.getName());
|
||||||
assertThat(authenticationResult.getClientRegistration().getClientSecret()).isNull();
|
assertThat(authenticationResult.getClientRegistration().getClientSecret()).isNull();
|
||||||
|
verifyNoInteractions(this.passwordEncoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -638,6 +665,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
|||||||
verify(this.registeredClientRepository).save(registeredClientCaptor.capture());
|
verify(this.registeredClientRepository).save(registeredClientCaptor.capture());
|
||||||
verify(this.authorizationService, times(2)).save(authorizationCaptor.capture());
|
verify(this.authorizationService, times(2)).save(authorizationCaptor.capture());
|
||||||
verify(this.jwtEncoder).encode(any());
|
verify(this.jwtEncoder).encode(any());
|
||||||
|
verify(this.passwordEncoder).encode(any());
|
||||||
|
|
||||||
// assert "registration" access token, which should be used for subsequent calls to client configuration endpoint
|
// assert "registration" access token, which should be used for subsequent calls to client configuration endpoint
|
||||||
OAuth2Authorization authorizationResult = authorizationCaptor.getAllValues().get(0);
|
OAuth2Authorization authorizationResult = authorizationCaptor.getAllValues().get(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user