Revert "URL encode client credentials"
This reverts commit 6cafa48369.
Issue gh-9610 gh-9862
Closes gh-10018
			
			
This commit is contained in:
		
							parent
							
								
									1cf377c250
								
							
						
					
					
						commit
						f55247e28a
					
				| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2021 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2020 the original author or authors.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
| 
						 | 
				
			
			@ -15,14 +15,6 @@
 | 
			
		|||
 */
 | 
			
		||||
package org.springframework.security.oauth2.client.endpoint;
 | 
			
		||||
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
import java.net.URLEncoder;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import reactor.core.publisher.Mono;
 | 
			
		||||
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +27,10 @@ import org.springframework.util.StringUtils;
 | 
			
		|||
import org.springframework.web.reactive.function.BodyInserters;
 | 
			
		||||
import org.springframework.web.reactive.function.client.ClientResponse;
 | 
			
		||||
import org.springframework.web.reactive.function.client.WebClient;
 | 
			
		||||
import reactor.core.publisher.Mono;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -90,19 +86,7 @@ abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T extend
 | 
			
		|||
		headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
 | 
			
		||||
		headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
 | 
			
		||||
		if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
 | 
			
		||||
			String clientId = encodeClientCredential(clientRegistration.getClientId());
 | 
			
		||||
			String clientSecret = encodeClientCredential(clientRegistration.getClientSecret());
 | 
			
		||||
			headers.setBasicAuth(clientId, clientSecret);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static String encodeClientCredential(String clientCredential) {
 | 
			
		||||
		try {
 | 
			
		||||
			return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString());
 | 
			
		||||
		}
 | 
			
		||||
		catch (UnsupportedEncodingException ex) {
 | 
			
		||||
			// Will not happen since UTF-8 is a standard charset
 | 
			
		||||
			throw new IllegalArgumentException(ex);
 | 
			
		||||
			headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2021 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2018 the original author or authors.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
| 
						 | 
				
			
			@ -15,11 +15,6 @@
 | 
			
		|||
 */
 | 
			
		||||
package org.springframework.security.oauth2.client.endpoint;
 | 
			
		||||
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
import java.net.URLEncoder;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
 | 
			
		||||
import org.springframework.core.convert.converter.Converter;
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +22,8 @@ import org.springframework.http.RequestEntity;
 | 
			
		|||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
 | 
			
		||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
 | 
			
		||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -47,23 +44,11 @@ final class OAuth2AuthorizationGrantRequestEntityUtils {
 | 
			
		|||
		HttpHeaders headers = new HttpHeaders();
 | 
			
		||||
		headers.addAll(DEFAULT_TOKEN_REQUEST_HEADERS);
 | 
			
		||||
		if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
 | 
			
		||||
			String clientId = encodeClientCredential(clientRegistration.getClientId());
 | 
			
		||||
			String clientSecret = encodeClientCredential(clientRegistration.getClientSecret());
 | 
			
		||||
			headers.setBasicAuth(clientId, clientSecret);
 | 
			
		||||
			headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
 | 
			
		||||
		}
 | 
			
		||||
		return headers;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static String encodeClientCredential(String clientCredential) {
 | 
			
		||||
		try {
 | 
			
		||||
			return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString());
 | 
			
		||||
		}
 | 
			
		||||
		catch (UnsupportedEncodingException ex) {
 | 
			
		||||
			// Will not happen since UTF-8 is a standard charset
 | 
			
		||||
			throw new IllegalArgumentException(ex);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static HttpHeaders getDefaultTokenRequestHeaders() {
 | 
			
		||||
		HttpHeaders headers = new HttpHeaders();
 | 
			
		||||
		headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,20 +15,13 @@
 | 
			
		|||
 */
 | 
			
		||||
package org.springframework.security.oauth2.client.endpoint;
 | 
			
		||||
 | 
			
		||||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
import java.net.URLEncoder;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.util.Base64;
 | 
			
		||||
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.HttpMethod;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.http.RequestEntity;
 | 
			
		||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
 | 
			
		||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
 | 
			
		||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
 | 
			
		||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 | 
			
		||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 | 
			
		||||
| 
						 | 
				
			
			@ -81,37 +74,4 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverterTests {
 | 
			
		|||
				AuthorizationGrantType.CLIENT_CREDENTIALS.getValue());
 | 
			
		||||
		assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("read write");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// gh-9610
 | 
			
		||||
	@SuppressWarnings("unchecked")
 | 
			
		||||
	@Test
 | 
			
		||||
	public void convertWhenSpecialCharactersThenConvertsWithEncodedClientCredentials()
 | 
			
		||||
			throws UnsupportedEncodingException {
 | 
			
		||||
		String clientCredentialWithAnsiKeyboardSpecialCharacters = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;',./ ";
 | 
			
		||||
		// @formatter:off
 | 
			
		||||
		ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials()
 | 
			
		||||
				.clientId(clientCredentialWithAnsiKeyboardSpecialCharacters)
 | 
			
		||||
				.clientSecret(clientCredentialWithAnsiKeyboardSpecialCharacters)
 | 
			
		||||
				.build();
 | 
			
		||||
		// @formatter:on
 | 
			
		||||
		OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest(
 | 
			
		||||
				clientRegistration);
 | 
			
		||||
		RequestEntity<?> requestEntity = this.converter.convert(clientCredentialsGrantRequest);
 | 
			
		||||
		assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST);
 | 
			
		||||
		assertThat(requestEntity.getUrl().toASCIIString())
 | 
			
		||||
				.isEqualTo(clientRegistration.getProviderDetails().getTokenUri());
 | 
			
		||||
		HttpHeaders headers = requestEntity.getHeaders();
 | 
			
		||||
		assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8);
 | 
			
		||||
		assertThat(headers.getContentType())
 | 
			
		||||
				.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
 | 
			
		||||
		String urlEncodedClientCredential = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters,
 | 
			
		||||
				StandardCharsets.UTF_8.toString());
 | 
			
		||||
		String clientCredentials = Base64.getEncoder().encodeToString(
 | 
			
		||||
				(urlEncodedClientCredential + ":" + urlEncodedClientCredential).getBytes(StandardCharsets.UTF_8));
 | 
			
		||||
		assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic " + clientCredentials);
 | 
			
		||||
		MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody();
 | 
			
		||||
		assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE))
 | 
			
		||||
				.isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue());
 | 
			
		||||
		assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2021 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2020 the original author or authors.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
| 
						 | 
				
			
			@ -16,17 +16,12 @@
 | 
			
		|||
 | 
			
		||||
package org.springframework.security.oauth2.client.endpoint;
 | 
			
		||||
 | 
			
		||||
import java.net.URLEncoder;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.util.Base64;
 | 
			
		||||
 | 
			
		||||
import okhttp3.mockwebserver.MockResponse;
 | 
			
		||||
import okhttp3.mockwebserver.MockWebServer;
 | 
			
		||||
import okhttp3.mockwebserver.RecordedRequest;
 | 
			
		||||
import org.junit.After;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
 | 
			
		||||
| 
						 | 
				
			
			@ -93,35 +88,6 @@ public class WebClientReactiveClientCredentialsTokenResponseClientTests {
 | 
			
		|||
		assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// gh-9610
 | 
			
		||||
	@Test
 | 
			
		||||
	public void getTokenResponseWhenSpecialCharactersThenSuccessWithEncodedClientCredentials() throws Exception {
 | 
			
		||||
		// @formatter:off
 | 
			
		||||
		enqueueJson("{\n"
 | 
			
		||||
			+ "  \"access_token\":\"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3\",\n"
 | 
			
		||||
			+ "  \"token_type\":\"bearer\",\n"
 | 
			
		||||
			+ "  \"expires_in\":3600,\n"
 | 
			
		||||
			+ "  \"refresh_token\":\"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk\",\n"
 | 
			
		||||
			+ "  \"scope\":\"create\"\n"
 | 
			
		||||
			+ "}");
 | 
			
		||||
		// @formatter:on
 | 
			
		||||
		String clientCredentialWithAnsiKeyboardSpecialCharacters = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;',./ ";
 | 
			
		||||
		OAuth2ClientCredentialsGrantRequest request = new OAuth2ClientCredentialsGrantRequest(
 | 
			
		||||
				this.clientRegistration.clientId(clientCredentialWithAnsiKeyboardSpecialCharacters)
 | 
			
		||||
						.clientSecret(clientCredentialWithAnsiKeyboardSpecialCharacters).build());
 | 
			
		||||
		OAuth2AccessTokenResponse response = this.client.getTokenResponse(request).block();
 | 
			
		||||
		RecordedRequest actualRequest = this.server.takeRequest();
 | 
			
		||||
		String body = actualRequest.getBody().readUtf8();
 | 
			
		||||
		assertThat(response.getAccessToken()).isNotNull();
 | 
			
		||||
		String urlEncodedClientCredentialecret = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters,
 | 
			
		||||
				StandardCharsets.UTF_8.toString());
 | 
			
		||||
		String clientCredentials = Base64.getEncoder()
 | 
			
		||||
				.encodeToString((urlEncodedClientCredentialecret + ":" + urlEncodedClientCredentialecret)
 | 
			
		||||
						.getBytes(StandardCharsets.UTF_8));
 | 
			
		||||
		assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic " + clientCredentials);
 | 
			
		||||
		assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void getTokenResponseWhenPostThenSuccess() throws Exception {
 | 
			
		||||
		ClientRegistration registration = this.clientRegistration
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue