Polish gh-14859
This commit is contained in:
parent
d0adb2aa70
commit
2598bf8c37
|
@ -92,9 +92,6 @@ val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
|
|||
tokenResponseClient.setRequestEntityConverter(requestEntityConverter)
|
||||
----
|
||||
======
|
||||
[NOTE]
|
||||
If you're using the `client-authentication-method: client_secret_basic` and you need to skip URL encoding,
|
||||
create a new `DefaultOAuth2TokenRequestHeadersConverter` and set it in the Request Entity Converter above.
|
||||
|
||||
=== Authenticate using `client_secret_jwt`
|
||||
|
||||
|
|
|
@ -42,8 +42,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||
abstract class AbstractOAuth2AuthorizationGrantRequestEntityConverter<T extends AbstractOAuth2AuthorizationGrantRequest>
|
||||
implements Converter<T, RequestEntity<?>> {
|
||||
|
||||
private Converter<T, HttpHeaders> headersConverter = DefaultOAuth2TokenRequestHeadersConverter
|
||||
.historicalConverter();
|
||||
private Converter<T, HttpHeaders> headersConverter = DefaultOAuth2TokenRequestHeadersConverter.withCharsetUtf8();
|
||||
|
||||
private Converter<T, MultiValueMap<String, String>> parametersConverter = this::createParameters;
|
||||
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
@ -23,87 +27,82 @@ import org.springframework.http.RequestEntity;
|
|||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Default {@link Converter} used to convert an
|
||||
* {@link AbstractOAuth2AuthorizationGrantRequest} to the {@link HttpHeaders} of aKk
|
||||
* {@link AbstractOAuth2AuthorizationGrantRequest} to the {@link HttpHeaders} of a
|
||||
* {@link RequestEntity} representation of an OAuth 2.0 Access Token Request for the
|
||||
* specific Authorization Grant.
|
||||
*
|
||||
* @author Peter Eastham
|
||||
* @author Joe Grandja
|
||||
* @see AbstractOAuth2AuthorizationGrantRequestEntityConverter
|
||||
* @author Steve Riesenberg
|
||||
* @since 6.3
|
||||
* @see AbstractOAuth2AuthorizationGrantRequestEntityConverter
|
||||
*/
|
||||
public final class DefaultOAuth2TokenRequestHeadersConverter<T extends AbstractOAuth2AuthorizationGrantRequest>
|
||||
implements Converter<T, HttpHeaders> {
|
||||
|
||||
private MediaType accept = MediaType.APPLICATION_JSON;
|
||||
private static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON,
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
private static final MediaType APPLICATION_FORM_URLENCODED_UTF8 = new MediaType(
|
||||
MediaType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8);
|
||||
|
||||
private List<MediaType> accept = List.of(MediaType.APPLICATION_JSON);
|
||||
|
||||
private MediaType contentType = MediaType.APPLICATION_FORM_URLENCODED;
|
||||
|
||||
private boolean encodeClientCredentialsIfRequired = true;
|
||||
private boolean encodeClientCredentials = true;
|
||||
|
||||
/**
|
||||
* Populates the headers for the token request.
|
||||
* @param grantRequest the grant request
|
||||
* Populates the default headers for the token request.
|
||||
* @param grantRequest the authorization grant request
|
||||
* @return the headers populated for the token request
|
||||
*/
|
||||
@Override
|
||||
public HttpHeaders convert(T grantRequest) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAccept(Collections.singletonList(accept));
|
||||
headers.setContentType(contentType);
|
||||
headers.setAccept(this.accept);
|
||||
headers.setContentType(this.contentType);
|
||||
ClientRegistration clientRegistration = grantRequest.getClientRegistration();
|
||||
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
|
||||
String clientId = encodeClientCredential(clientRegistration.getClientId());
|
||||
String clientSecret = encodeClientCredential(clientRegistration.getClientSecret());
|
||||
String clientId = encodeClientCredentialIfRequired(clientRegistration.getClientId());
|
||||
String clientSecret = encodeClientCredentialIfRequired(clientRegistration.getClientSecret());
|
||||
headers.setBasicAuth(clientId, clientSecret);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String encodeClientCredential(String clientCredential) {
|
||||
String encodedCredential = clientCredential;
|
||||
if (this.encodeClientCredentialsIfRequired) {
|
||||
encodedCredential = URLEncoder.encode(clientCredential, StandardCharsets.UTF_8);
|
||||
private String encodeClientCredentialIfRequired(String clientCredential) {
|
||||
if (!this.encodeClientCredentials) {
|
||||
return clientCredential;
|
||||
}
|
||||
return encodedCredential;
|
||||
return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the behavior for if this URL Encoding the Client Credentials during the
|
||||
* conversion.
|
||||
* @param encodeClientCredentialsIfRequired if false, no URL encoding will happen
|
||||
* Sets whether the client credentials of the {@code Authorization} header will be
|
||||
* encoded using the {@code application/x-www-form-urlencoded} encoding algorithm
|
||||
* according to RFC 6749. Default is {@code true}.
|
||||
* @param encodeClientCredentials whether the client credentials will be encoded
|
||||
* @see <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1">2.3.1 Client
|
||||
* Password</a>
|
||||
*/
|
||||
public void setEncodeClientCredentials(boolean encodeClientCredentialsIfRequired) {
|
||||
this.encodeClientCredentialsIfRequired = encodeClientCredentialsIfRequired;
|
||||
public void setEncodeClientCredentials(boolean encodeClientCredentials) {
|
||||
this.encodeClientCredentials = encodeClientCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* MediaType to set for the Accept header. Default is application/json
|
||||
* @param accept MediaType to use for the Accept header
|
||||
* Creates a {@link DefaultOAuth2TokenRequestHeadersConverter} that populates default
|
||||
* {@link HttpHeaders} that includes {@code charset=UTF-8} on both the {@code Accept}
|
||||
* and {@code Content-Type} headers to provide backwards compatibility for
|
||||
* {@link AbstractOAuth2AuthorizationGrantRequestEntityConverter}.
|
||||
* @return the default headers converter
|
||||
*/
|
||||
private void setAccept(MediaType accept) {
|
||||
this.accept = accept;
|
||||
}
|
||||
|
||||
/**
|
||||
* MediaType to set for the Content Type header. Default is
|
||||
* application/x-www-form-urlencoded
|
||||
* @param contentType MediaType to use for the Content Type header
|
||||
*/
|
||||
private void setContentType(MediaType contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
static <T extends AbstractOAuth2AuthorizationGrantRequest> DefaultOAuth2TokenRequestHeadersConverter<T> historicalConverter() {
|
||||
static <T extends AbstractOAuth2AuthorizationGrantRequest> DefaultOAuth2TokenRequestHeadersConverter<T> withCharsetUtf8() {
|
||||
DefaultOAuth2TokenRequestHeadersConverter<T> converter = new DefaultOAuth2TokenRequestHeadersConverter<>();
|
||||
converter.setAccept(MediaType.APPLICATION_JSON_UTF8);
|
||||
converter.setContentType(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
|
||||
converter.accept = List.of(APPLICATION_JSON_UTF8);
|
||||
converter.contentType = APPLICATION_FORM_URLENCODED_UTF8;
|
||||
return converter;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultOAuth2TokenRequestHeadersConverter}.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class DefaultOAuth2TokenRequestHeadersConverterTests {
|
||||
|
||||
private static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON,
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
private static final MediaType APPLICATION_FORM_URLENCODED_UTF8 = new MediaType(
|
||||
MediaType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8);
|
||||
|
||||
private DefaultOAuth2TokenRequestHeadersConverter<OAuth2ClientCredentialsGrantRequest> converter;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.converter = new DefaultOAuth2TokenRequestHeadersConverter<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenEncodeClientCredentialsTrueThenConvertsWithUrlEncoding() {
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials()
|
||||
.clientId("clientId")
|
||||
.clientSecret("clientSecret=")
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2ClientCredentialsGrantRequest grantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration);
|
||||
HttpHeaders defaultHeaders = this.converter.convert(grantRequest);
|
||||
assertThat(defaultHeaders.getAccept()).containsExactly(MediaType.APPLICATION_JSON);
|
||||
assertThat(defaultHeaders.getContentType()).isEqualTo(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
assertThat(defaultHeaders.getFirst(HttpHeaders.AUTHORIZATION))
|
||||
.isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0JTNE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenEncodeClientCredentialsFalseThenConvertsWithoutUrlEncoding() {
|
||||
this.converter.setEncodeClientCredentials(false);
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials()
|
||||
.clientId("clientId")
|
||||
.clientSecret("clientSecret=")
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2ClientCredentialsGrantRequest grantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration);
|
||||
HttpHeaders defaultHeaders = this.converter.convert(grantRequest);
|
||||
assertThat(defaultHeaders.getAccept()).containsExactly(MediaType.APPLICATION_JSON);
|
||||
assertThat(defaultHeaders.getContentType()).isEqualTo(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
assertThat(defaultHeaders.getFirst(HttpHeaders.AUTHORIZATION))
|
||||
.isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0PQ==");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenWithCharsetUtf8AndEncodeClientCredentialsTrueThenConvertsWithUrlEncoding() {
|
||||
this.converter = DefaultOAuth2TokenRequestHeadersConverter.withCharsetUtf8();
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials()
|
||||
.clientId("clientId")
|
||||
.clientSecret("clientSecret=")
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2ClientCredentialsGrantRequest grantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration);
|
||||
HttpHeaders defaultHeaders = this.converter.convert(grantRequest);
|
||||
assertThat(defaultHeaders.getAccept()).containsExactly(APPLICATION_JSON_UTF8);
|
||||
assertThat(defaultHeaders.getContentType()).isEqualTo(APPLICATION_FORM_URLENCODED_UTF8);
|
||||
assertThat(defaultHeaders.getFirst(HttpHeaders.AUTHORIZATION))
|
||||
.isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0JTNE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenWithCharsetUtf8EncodeClientCredentialsFalseThenConvertsWithoutUrlEncoding() {
|
||||
this.converter = DefaultOAuth2TokenRequestHeadersConverter.withCharsetUtf8();
|
||||
this.converter.setEncodeClientCredentials(false);
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials()
|
||||
.clientId("clientId")
|
||||
.clientSecret("clientSecret=")
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2ClientCredentialsGrantRequest grantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration);
|
||||
HttpHeaders defaultHeaders = this.converter.convert(grantRequest);
|
||||
assertThat(defaultHeaders.getAccept()).containsExactly(APPLICATION_JSON_UTF8);
|
||||
assertThat(defaultHeaders.getContentType()).isEqualTo(APPLICATION_FORM_URLENCODED_UTF8);
|
||||
assertThat(defaultHeaders.getFirst(HttpHeaders.AUTHORIZATION))
|
||||
.isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0PQ==");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
|
@ -122,7 +122,12 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverterTests {
|
|||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void convertWhenGrantRequestValidThenConverts() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration()
|
||||
.clientId("clientId")
|
||||
.clientSecret("clientSecret=")
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2AuthorizationExchange authorizationExchange = TestOAuth2AuthorizationExchanges.success();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorizationExchange.getAuthorizationRequest();
|
||||
OAuth2AuthorizationResponse authorizationResponse = authorizationExchange.getAuthorizationResponse();
|
||||
|
@ -136,7 +141,7 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverterTests {
|
|||
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8);
|
||||
assertThat(headers.getContentType())
|
||||
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
|
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic ");
|
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0JTNE");
|
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody();
|
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE))
|
||||
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 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.
|
||||
|
@ -110,10 +110,7 @@ public class OAuth2PasswordGrantRequestEntityConverterTests {
|
|||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void convertWhenGrantRequestValidThenConverts() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.password()
|
||||
.clientId("clientId")
|
||||
.clientSecret("clientSecret=")
|
||||
.build();
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.password().build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, "user1",
|
||||
"password");
|
||||
RequestEntity<?> requestEntity = this.converter.convert(passwordGrantRequest);
|
||||
|
@ -124,7 +121,7 @@ public class OAuth2PasswordGrantRequestEntityConverterTests {
|
|||
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8);
|
||||
assertThat(headers.getContentType())
|
||||
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
|
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0JTNE");
|
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic ");
|
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody();
|
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE))
|
||||
.isEqualTo(AuthorizationGrantType.PASSWORD.getValue());
|
||||
|
@ -133,34 +130,4 @@ public class OAuth2PasswordGrantRequestEntityConverterTests {
|
|||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void convertWhenGrantRequestValidThenConvertsWithoutUrlEncoding() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.password()
|
||||
.clientId("clientId")
|
||||
.clientSecret("clientSecret=")
|
||||
.build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, "user1",
|
||||
"password=");
|
||||
DefaultOAuth2TokenRequestHeadersConverter<OAuth2PasswordGrantRequest> headersConverter = DefaultOAuth2TokenRequestHeadersConverter
|
||||
.historicalConverter();
|
||||
headersConverter.setEncodeClientCredentials(false);
|
||||
this.converter.setHeadersConverter(headersConverter);
|
||||
RequestEntity<?> requestEntity = this.converter.convert(passwordGrantRequest);
|
||||
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"));
|
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0PQ==");
|
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody();
|
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE))
|
||||
.isEqualTo(AuthorizationGrantType.PASSWORD.getValue());
|
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.USERNAME)).isEqualTo("user1");
|
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.PASSWORD)).isEqualTo("password=");
|
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue