diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java index a2ddbe4a..da27b5e4 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java @@ -203,6 +203,10 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe .clientSecret(this.passwordEncoder.encode(registeredClient.getClientSecret())) .build(); this.registeredClientRepository.save(updatedRegisteredClient); + if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistrationAuthentication.getClientRegistration().getTokenEndpointAuthenticationMethod())) { + // gh-1344 Return the hashed client_secret + registeredClient = updatedRegisteredClient; + } } else { this.registeredClientRepository.save(registeredClient); } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java index ca178ed6..12445530 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java @@ -68,6 +68,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; @@ -103,6 +104,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; @@ -400,6 +403,55 @@ public class OidcClientRegistrationTests { .andReturn(); } + // gh-1344 + @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