From a1f42f184311290776a6f571ac3a939ba42a6f12 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 20 Jun 2023 05:09:07 -0400 Subject: [PATCH 1/6] Release 0.4.3 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a2bfaae0..0e2e3487 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=0.4.3-SNAPSHOT +version=0.4.3 org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true org.gradle.caching=true From 647e873b30c591ca9f97d4b4c6f9392955d112d8 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 20 Jun 2023 05:17:35 -0400 Subject: [PATCH 2/6] Next Development Version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0e2e3487..f285b9fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=0.4.3 +version=0.4.4-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true org.gradle.caching=true From 47b87cd76e9c016e4672aaf6d7bbca56fba6ff74 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 20 Jun 2023 05:51:17 -0400 Subject: [PATCH 3/6] Release 1.0.3 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 23544b30..7dc1e794 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=1.0.3-SNAPSHOT +version=1.0.3 org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true org.gradle.caching=true From 74040a60bdf27ea9d6f9f1b92eb99d50ea886118 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 20 Jun 2023 06:00:31 -0400 Subject: [PATCH 4/6] Next Development Version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7dc1e794..cee76319 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=1.0.3 +version=1.0.4-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true org.gradle.caching=true From 9109d3714b0bea33f9e91d570aaf4683d7c600a2 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 2 Oct 2023 06:46:50 +0900 Subject: [PATCH 5/6] Fix to return hashed client_secret when registering with client_secret_jwt Closes gh-1344 --- ...entRegistrationAuthenticationProvider.java | 22 +++++--- .../OidcClientRegistrationTests.java | 51 +++++++++++++++++++ 2 files changed, 65 insertions(+), 8 deletions(-) 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..ded933fe 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 @@ -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 clientRegistrationClaims = this.clientRegistrationConverter.convert(registeredClient).getClaims(); + Map clientRegistrationClaims = this.clientRegistrationConverter.convert(registeredClientForResponse).getClaims(); OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(clientRegistrationClaims) .registrationAccessToken(registeredClientAuthorization.getAccessToken().getToken().getTokenValue()) .build(); 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 d6122ebb..f19861cf 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 @@ -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 From 6d21a65ff446e49120e2c3e60db89b5c63168de5 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 11 Oct 2023 12:04:44 -0400 Subject: [PATCH 6/6] Polish gh-1345 --- ...entRegistrationAuthenticationProvider.java | 22 +++++++++---------- .../OidcClientRegistrationTests.java | 1 + 2 files changed, 11 insertions(+), 12 deletions(-) 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 ded933fe..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 @@ -196,28 +196,26 @@ 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 - String encodedClientSecret = this.passwordEncoder.encode(registeredClient.getClientSecret()); - registeredClientForDBBuilder = registeredClientForDBBuilder - .clientSecret(encodedClientSecret); + RegisteredClient updatedRegisteredClient = RegisteredClient.from(registeredClient) + .clientSecret(this.passwordEncoder.encode(registeredClient.getClientSecret())) + .build(); + this.registeredClientRepository.save(updatedRegisteredClient); if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistrationAuthentication.getClientRegistration().getTokenEndpointAuthenticationMethod())) { - registeredClientForResponseBuilder.clientSecret(encodedClientSecret); + // gh-1344 Return the hashed client_secret + registeredClient = updatedRegisteredClient; } + } else { + this.registeredClientRepository.save(registeredClient); } - this.registeredClientRepository.save(registeredClientForDBBuilder.build()); - - RegisteredClient registeredClientForResponse = registeredClientForResponseBuilder.build(); - if (this.logger.isTraceEnabled()) { this.logger.trace("Saved registered client"); } - OAuth2Authorization registeredClientAuthorization = registerAccessToken(registeredClientForResponse); + OAuth2Authorization registeredClientAuthorization = registerAccessToken(registeredClient); // Invalidate the "initial" access token as it can only be used once authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorization.getAccessToken().getToken()); @@ -230,7 +228,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe this.logger.trace("Saved authorization with invalidated initial access token"); } - Map clientRegistrationClaims = this.clientRegistrationConverter.convert(registeredClientForResponse).getClaims(); + Map clientRegistrationClaims = this.clientRegistrationConverter.convert(registeredClient).getClaims(); OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(clientRegistrationClaims) .registrationAccessToken(registeredClientAuthorization.getAccessToken().getToken().getTokenValue()) .build(); 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 f19861cf..0d1f6915 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 @@ -402,6 +402,7 @@ public class OidcClientRegistrationTests { .andReturn(); } + // gh-1344 @Test public void requestWhenClientRegistersWithClientSecretJwtThenClientAuthenticationSuccess() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire();