mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-31 06:38:42 +00:00 
			
		
		
		
	URL encode client credentials
Closes gh-9610
This commit is contained in:
		
							parent
							
								
									82d471a592
								
							
						
					
					
						commit
						e6c268add0
					
				| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright 2002-2020 the original author or authors. | ||||
|  * Copyright 2002-2021 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,6 +16,9 @@ | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
| @ -97,7 +100,19 @@ public abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T | ||||
| 		headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); | ||||
| 		if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientRegistration.getClientAuthenticationMethod()) | ||||
| 				|| ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { | ||||
| 			headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); | ||||
| 			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); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright 2002-2020 the original author or authors. | ||||
|  * Copyright 2002-2021 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,6 +16,9 @@ | ||||
| 
 | ||||
| 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; | ||||
| @ -48,11 +51,23 @@ final class OAuth2AuthorizationGrantRequestEntityUtils { | ||||
| 		headers.addAll(DEFAULT_TOKEN_REQUEST_HEADERS); | ||||
| 		if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientRegistration.getClientAuthenticationMethod()) | ||||
| 				|| ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { | ||||
| 			headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); | ||||
| 			String clientId = encodeClientCredential(clientRegistration.getClientId()); | ||||
| 			String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); | ||||
| 			headers.setBasicAuth(clientId, clientSecret); | ||||
| 		} | ||||
| 		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)); | ||||
|  | ||||
| @ -16,6 +16,11 @@ | ||||
| 
 | ||||
| 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.mockito.InOrder; | ||||
| @ -128,4 +133,37 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverterTests { | ||||
| 		assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes()); | ||||
| 	} | ||||
| 
 | ||||
| 	// 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-2020 the original author or authors. | ||||
|  * Copyright 2002-2021 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,6 +16,10 @@ | ||||
| 
 | ||||
| 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; | ||||
| @ -89,6 +93,35 @@ 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user