Fix to return hashed client_secret when registering with client_secret_jwt

Closes gh-1344
This commit is contained in:
Yuta Saito
2023-10-02 06:46:50 +09:00
committed by Joe Grandja
parent 647e873b30
commit 9109d3714b
2 changed files with 65 additions and 8 deletions

View File

@@ -196,22 +196,28 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
}
RegisteredClient registeredClient = this.registeredClientConverter.convert(clientRegistrationAuthentication.getClientRegistration());
RegisteredClient.Builder registeredClientForDBBuilder = RegisteredClient.from(registeredClient);
RegisteredClient.Builder registeredClientForResponseBuilder = RegisteredClient.from(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);
String encodedClientSecret = this.passwordEncoder.encode(registeredClient.getClientSecret());
registeredClientForDBBuilder = registeredClientForDBBuilder
.clientSecret(encodedClientSecret);
if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistrationAuthentication.getClientRegistration().getTokenEndpointAuthenticationMethod())) {
registeredClientForResponseBuilder.clientSecret(encodedClientSecret);
}
}
this.registeredClientRepository.save(registeredClientForDBBuilder.build());
RegisteredClient registeredClientForResponse = registeredClientForResponseBuilder.build();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved registered client");
}
OAuth2Authorization registeredClientAuthorization = registerAccessToken(registeredClient);
OAuth2Authorization registeredClientAuthorization = registerAccessToken(registeredClientForResponse);
// Invalidate the "initial" access token as it can only be used once
authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorization.getAccessToken().getToken());
@@ -224,7 +230,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
this.logger.trace("Saved authorization with invalidated initial access token");
}
Map<String, Object> clientRegistrationClaims = this.clientRegistrationConverter.convert(registeredClient).getClaims();
Map<String, Object> clientRegistrationClaims = this.clientRegistrationConverter.convert(registeredClientForResponse).getClaims();
OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(clientRegistrationClaims)
.registrationAccessToken(registeredClientAuthorization.getAccessToken().getToken().getTokenValue())
.build();

View File

@@ -67,6 +67,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
@@ -102,6 +103,8 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.util.UriComponentsBuilder;
import javax.crypto.spec.SecretKeySpec;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.ArgumentMatchers.any;
@@ -399,6 +402,54 @@ public class OidcClientRegistrationTests {
.andReturn();
}
@Test
public void requestWhenClientRegistersWithClientSecretJwtThenClientAuthenticationSuccess() 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())
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue())
.scope("scope1")
.scope("scope2")
.build();
// @formatter:on
OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration);
JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256)
.build();
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
JwtClaimsSet jwtClaimsSet = JwtClaimsSet.builder()
.issuer(clientRegistrationResponse.getClientId())
.subject(clientRegistrationResponse.getClientId())
.audience(Collections.singletonList(asUrl(this.authorizationServerSettings.getIssuer(), this.authorizationServerSettings.getTokenEndpoint())))
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.build();
JWKSet jwkSet = new JWKSet(TestJwks.jwk(
new SecretKeySpec(clientRegistrationResponse.getClientSecret().getBytes(), "HS256")).build());
JwtEncoder jwtClientAssertionEncoder = new NimbusJwtEncoder((jwkSelector, securityContext) -> jwkSelector.select(jwkSet));
Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet));
this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, "scope1")
.param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
.param(OAuth2ParameterNames.CLIENT_ASSERTION, jwtAssertion.getTokenValue())
.param(OAuth2ParameterNames.CLIENT_ID, clientRegistrationResponse.getClientId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.scope").value("scope1"));
}
private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
// ***** (1) Obtain the "initial" access token used for registering the client