Kaynağa Gözat

Fix to return hashed client_secret when registering with client_secret_jwt

Closes gh-1344
Yuta Saito 1 yıl önce
ebeveyn
işleme
9109d3714b

+ 14 - 8
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<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();

+ 51 - 0
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