Skip to content

Commit 5243b1b

Browse files
author
Steve Riesenberg
committed
URL encode client credentials
Closes gh-9610
1 parent 43f3de7 commit 5243b1b

File tree

4 files changed

+107
-5
lines changed

4 files changed

+107
-5
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.security.oauth2.client.endpoint;
1818

19+
import java.io.UnsupportedEncodingException;
20+
import java.net.URLEncoder;
21+
import java.nio.charset.StandardCharsets;
1922
import java.util.Collections;
2023
import java.util.Set;
2124

@@ -96,7 +99,19 @@ private void populateTokenRequestHeaders(T grantRequest, HttpHeaders headers) {
9699
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
97100
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
98101
if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
99-
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
102+
String clientId = encodeClientCredential(clientRegistration.getClientId());
103+
String clientSecret = encodeClientCredential(clientRegistration.getClientSecret());
104+
headers.setBasicAuth(clientId, clientSecret);
105+
}
106+
}
107+
108+
private static String encodeClientCredential(String clientCredential) {
109+
try {
110+
return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString());
111+
}
112+
catch (UnsupportedEncodingException ex) {
113+
// Will not happen since UTF-8 is a standard charset
114+
throw new IllegalArgumentException(ex);
100115
}
101116
}
102117

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationGrantRequestEntityUtils.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.security.oauth2.client.endpoint;
1818

19+
import java.io.UnsupportedEncodingException;
20+
import java.net.URLEncoder;
21+
import java.nio.charset.StandardCharsets;
1922
import java.util.Collections;
2023

2124
import org.springframework.core.convert.converter.Converter;
@@ -47,11 +50,23 @@ static HttpHeaders getTokenRequestHeaders(ClientRegistration clientRegistration)
4750
HttpHeaders headers = new HttpHeaders();
4851
headers.addAll(DEFAULT_TOKEN_REQUEST_HEADERS);
4952
if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
50-
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
53+
String clientId = encodeClientCredential(clientRegistration.getClientId());
54+
String clientSecret = encodeClientCredential(clientRegistration.getClientSecret());
55+
headers.setBasicAuth(clientId, clientSecret);
5156
}
5257
return headers;
5358
}
5459

60+
private static String encodeClientCredential(String clientCredential) {
61+
try {
62+
return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString());
63+
}
64+
catch (UnsupportedEncodingException ex) {
65+
// Will not happen since UTF-8 is a standard charset
66+
throw new IllegalArgumentException(ex);
67+
}
68+
}
69+
5570
private static HttpHeaders getDefaultTokenRequestHeaders() {
5671
HttpHeaders headers = new HttpHeaders();
5772
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616

1717
package org.springframework.security.oauth2.client.endpoint;
1818

19+
import java.io.UnsupportedEncodingException;
20+
import java.net.URLEncoder;
21+
import java.nio.charset.StandardCharsets;
22+
import java.util.Base64;
23+
1924
import org.junit.Before;
2025
import org.junit.Test;
2126

@@ -24,6 +29,7 @@
2429
import org.springframework.http.MediaType;
2530
import org.springframework.http.RequestEntity;
2631
import org.springframework.security.oauth2.client.registration.ClientRegistration;
32+
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
2733
import org.springframework.security.oauth2.core.AuthorizationGrantType;
2834
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
2935
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@@ -76,4 +82,37 @@ public void convertWhenGrantRequestValidThenConverts() {
7682
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("read write");
7783
}
7884

85+
// gh-9610
86+
@SuppressWarnings("unchecked")
87+
@Test
88+
public void convertWhenSpecialCharactersThenConvertsWithEncodedClientCredentials()
89+
throws UnsupportedEncodingException {
90+
String clientCredentialWithAnsiKeyboardSpecialCharacters = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;',./ ";
91+
// @formatter:off
92+
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials()
93+
.clientId(clientCredentialWithAnsiKeyboardSpecialCharacters)
94+
.clientSecret(clientCredentialWithAnsiKeyboardSpecialCharacters)
95+
.build();
96+
// @formatter:on
97+
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest(
98+
clientRegistration);
99+
RequestEntity<?> requestEntity = this.converter.convert(clientCredentialsGrantRequest);
100+
assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST);
101+
assertThat(requestEntity.getUrl().toASCIIString())
102+
.isEqualTo(clientRegistration.getProviderDetails().getTokenUri());
103+
HttpHeaders headers = requestEntity.getHeaders();
104+
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8);
105+
assertThat(headers.getContentType())
106+
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
107+
String urlEncodedClientCredential = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters,
108+
StandardCharsets.UTF_8.toString());
109+
String clientCredentials = Base64.getEncoder().encodeToString(
110+
(urlEncodedClientCredential + ":" + urlEncodedClientCredential).getBytes(StandardCharsets.UTF_8));
111+
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic " + clientCredentials);
112+
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody();
113+
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE))
114+
.isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue());
115+
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes());
116+
}
117+
79118
}

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.security.oauth2.client.endpoint;
1818

19+
import java.net.URLEncoder;
20+
import java.nio.charset.StandardCharsets;
21+
import java.util.Base64;
22+
1923
import okhttp3.mockwebserver.MockResponse;
2024
import okhttp3.mockwebserver.MockWebServer;
2125
import okhttp3.mockwebserver.RecordedRequest;
@@ -89,6 +93,35 @@ public void getTokenResponseWhenHeaderThenSuccess() throws Exception {
8993
assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser");
9094
}
9195

96+
// gh-9610
97+
@Test
98+
public void getTokenResponseWhenSpecialCharactersThenSuccessWithEncodedClientCredentials() throws Exception {
99+
// @formatter:off
100+
enqueueJson("{\n"
101+
+ " \"access_token\":\"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3\",\n"
102+
+ " \"token_type\":\"bearer\",\n"
103+
+ " \"expires_in\":3600,\n"
104+
+ " \"refresh_token\":\"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk\",\n"
105+
+ " \"scope\":\"create\"\n"
106+
+ "}");
107+
// @formatter:on
108+
String clientCredentialWithAnsiKeyboardSpecialCharacters = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;',./ ";
109+
OAuth2ClientCredentialsGrantRequest request = new OAuth2ClientCredentialsGrantRequest(
110+
this.clientRegistration.clientId(clientCredentialWithAnsiKeyboardSpecialCharacters)
111+
.clientSecret(clientCredentialWithAnsiKeyboardSpecialCharacters).build());
112+
OAuth2AccessTokenResponse response = this.client.getTokenResponse(request).block();
113+
RecordedRequest actualRequest = this.server.takeRequest();
114+
String body = actualRequest.getBody().readUtf8();
115+
assertThat(response.getAccessToken()).isNotNull();
116+
String urlEncodedClientCredentialecret = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters,
117+
StandardCharsets.UTF_8.toString());
118+
String clientCredentials = Base64.getEncoder()
119+
.encodeToString((urlEncodedClientCredentialecret + ":" + urlEncodedClientCredentialecret)
120+
.getBytes(StandardCharsets.UTF_8));
121+
assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic " + clientCredentials);
122+
assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser");
123+
}
124+
92125
@Test
93126
public void getTokenResponseWhenPostThenSuccess() throws Exception {
94127
ClientRegistration registration = this.clientRegistration

0 commit comments

Comments
 (0)