38 changed files with 118 additions and 4424 deletions
@ -1,170 +0,0 @@
@@ -1,170 +0,0 @@
|
||||
/* |
||||
* 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.net.URI; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.RequestEntity; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
/** |
||||
* Base implementation of a {@link Converter} that converts the provided |
||||
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link RequestEntity} |
||||
* representation of an OAuth 2.0 Access Token Request for the Authorization Grant. |
||||
* |
||||
* @param <T> the type of {@link AbstractOAuth2AuthorizationGrantRequest} |
||||
* @author Joe Grandja |
||||
* @since 5.5 |
||||
* @see Converter |
||||
* @see AbstractOAuth2AuthorizationGrantRequest |
||||
* @see RequestEntity |
||||
*/ |
||||
abstract class AbstractOAuth2AuthorizationGrantRequestEntityConverter<T extends AbstractOAuth2AuthorizationGrantRequest> |
||||
implements Converter<T, RequestEntity<?>> { |
||||
|
||||
private Converter<T, HttpHeaders> headersConverter = DefaultOAuth2TokenRequestHeadersConverter.withCharsetUtf8(); |
||||
|
||||
private Converter<T, MultiValueMap<String, String>> parametersConverter = this::createParameters; |
||||
|
||||
@Override |
||||
public RequestEntity<?> convert(T authorizationGrantRequest) { |
||||
HttpHeaders headers = getHeadersConverter().convert(authorizationGrantRequest); |
||||
MultiValueMap<String, String> parameters = getParametersConverter().convert(authorizationGrantRequest); |
||||
URI uri = UriComponentsBuilder |
||||
.fromUriString(authorizationGrantRequest.getClientRegistration().getProviderDetails().getTokenUri()) |
||||
.build() |
||||
.toUri(); |
||||
return new RequestEntity<>(parameters, headers, HttpMethod.POST, uri); |
||||
} |
||||
|
||||
/** |
||||
* Returns a {@link MultiValueMap} of the parameters used in the OAuth 2.0 Access |
||||
* Token Request body. |
||||
* @param authorizationGrantRequest the authorization grant request |
||||
* @return a {@link MultiValueMap} of the parameters used in the OAuth 2.0 Access |
||||
* Token Request body |
||||
*/ |
||||
abstract MultiValueMap<String, String> createParameters(T authorizationGrantRequest); |
||||
|
||||
/** |
||||
* Returns the {@link Converter} used for converting the |
||||
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link HttpHeaders} |
||||
* used in the OAuth 2.0 Access Token Request headers. |
||||
* @return the {@link Converter} used for converting the |
||||
* {@link OAuth2AuthorizationCodeGrantRequest} to {@link HttpHeaders} |
||||
*/ |
||||
final Converter<T, HttpHeaders> getHeadersConverter() { |
||||
return this.headersConverter; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link Converter} used for converting the |
||||
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link HttpHeaders} |
||||
* used in the OAuth 2.0 Access Token Request headers. |
||||
* @param headersConverter the {@link Converter} used for converting the |
||||
* {@link OAuth2AuthorizationCodeGrantRequest} to {@link HttpHeaders} |
||||
*/ |
||||
public final void setHeadersConverter(Converter<T, HttpHeaders> headersConverter) { |
||||
Assert.notNull(headersConverter, "headersConverter cannot be null"); |
||||
this.headersConverter = headersConverter; |
||||
} |
||||
|
||||
/** |
||||
* Add (compose) the provided {@code headersConverter} to the current |
||||
* {@link Converter} used for converting the |
||||
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link HttpHeaders} |
||||
* used in the OAuth 2.0 Access Token Request headers. |
||||
* @param headersConverter the {@link Converter} to add (compose) to the current |
||||
* {@link Converter} used for converting the |
||||
* {@link OAuth2AuthorizationCodeGrantRequest} to a {@link HttpHeaders} |
||||
*/ |
||||
public final void addHeadersConverter(Converter<T, HttpHeaders> headersConverter) { |
||||
Assert.notNull(headersConverter, "headersConverter cannot be null"); |
||||
Converter<T, HttpHeaders> currentHeadersConverter = this.headersConverter; |
||||
this.headersConverter = (authorizationGrantRequest) -> { |
||||
// Append headers using a Composite Converter
|
||||
HttpHeaders headers = currentHeadersConverter.convert(authorizationGrantRequest); |
||||
if (headers == null) { |
||||
headers = new HttpHeaders(); |
||||
} |
||||
HttpHeaders headersToAdd = headersConverter.convert(authorizationGrantRequest); |
||||
if (headersToAdd != null) { |
||||
headers.addAll(headersToAdd); |
||||
} |
||||
return headers; |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Returns the {@link Converter} used for converting the |
||||
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link MultiValueMap} |
||||
* of the parameters used in the OAuth 2.0 Access Token Request body. |
||||
* @return the {@link Converter} used for converting the |
||||
* {@link OAuth2AuthorizationCodeGrantRequest} to a {@link MultiValueMap} of the |
||||
* parameters |
||||
*/ |
||||
final Converter<T, MultiValueMap<String, String>> getParametersConverter() { |
||||
return this.parametersConverter; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link Converter} used for converting the |
||||
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link MultiValueMap} |
||||
* of the parameters used in the OAuth 2.0 Access Token Request body. |
||||
* @param parametersConverter the {@link Converter} used for converting the |
||||
* {@link OAuth2AuthorizationCodeGrantRequest} to a {@link MultiValueMap} of the |
||||
* parameters |
||||
*/ |
||||
public final void setParametersConverter(Converter<T, MultiValueMap<String, String>> parametersConverter) { |
||||
Assert.notNull(parametersConverter, "parametersConverter cannot be null"); |
||||
this.parametersConverter = parametersConverter; |
||||
} |
||||
|
||||
/** |
||||
* Add (compose) the provided {@code parametersConverter} to the current |
||||
* {@link Converter} used for converting the |
||||
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link MultiValueMap} |
||||
* of the parameters used in the OAuth 2.0 Access Token Request body. |
||||
* @param parametersConverter the {@link Converter} to add (compose) to the current |
||||
* {@link Converter} used for converting the |
||||
* {@link OAuth2AuthorizationCodeGrantRequest} to a {@link MultiValueMap} of the |
||||
* parameters |
||||
*/ |
||||
public final void addParametersConverter(Converter<T, MultiValueMap<String, String>> parametersConverter) { |
||||
Assert.notNull(parametersConverter, "parametersConverter cannot be null"); |
||||
Converter<T, MultiValueMap<String, String>> currentParametersConverter = this.parametersConverter; |
||||
this.parametersConverter = (authorizationGrantRequest) -> { |
||||
// Append parameters using a Composite Converter
|
||||
MultiValueMap<String, String> parameters = currentParametersConverter.convert(authorizationGrantRequest); |
||||
if (parameters == null) { |
||||
parameters = new LinkedMultiValueMap<>(); |
||||
} |
||||
MultiValueMap<String, String> parametersToAdd = parametersConverter.convert(authorizationGrantRequest); |
||||
if (parametersToAdd != null) { |
||||
parameters.addAll(parametersToAdd); |
||||
} |
||||
return parameters; |
||||
}; |
||||
} |
||||
|
||||
} |
||||
@ -1,48 +0,0 @@
@@ -1,48 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2023 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 org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.RequestEntity; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.util.Assert; |
||||
|
||||
class ClientAuthenticationMethodValidatingRequestEntityConverter<T extends AbstractOAuth2AuthorizationGrantRequest> |
||||
implements Converter<T, RequestEntity<?>> { |
||||
|
||||
private final Converter<T, RequestEntity<?>> delegate; |
||||
|
||||
ClientAuthenticationMethodValidatingRequestEntityConverter(Converter<T, RequestEntity<?>> delegate) { |
||||
this.delegate = delegate; |
||||
} |
||||
|
||||
@Override |
||||
public RequestEntity<?> convert(T grantRequest) { |
||||
ClientRegistration clientRegistration = grantRequest.getClientRegistration(); |
||||
ClientAuthenticationMethod clientAuthenticationMethod = clientRegistration.getClientAuthenticationMethod(); |
||||
String registrationId = clientRegistration.getRegistrationId(); |
||||
boolean supportedClientAuthenticationMethod = clientAuthenticationMethod.equals(ClientAuthenticationMethod.NONE) |
||||
|| clientAuthenticationMethod.equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) |
||||
|| clientAuthenticationMethod.equals(ClientAuthenticationMethod.CLIENT_SECRET_POST); |
||||
Assert.isTrue(supportedClientAuthenticationMethod, () -> String.format( |
||||
"This class supports `client_secret_basic`, `client_secret_post`, and `none` by default. Client [%s] is using [%s] instead. Please use a supported client authentication method, or use `setRequestEntityConverter` to supply an instance that supports [%s].", |
||||
registrationId, clientAuthenticationMethod, clientAuthenticationMethod)); |
||||
return this.delegate.convert(grantRequest); |
||||
} |
||||
|
||||
} |
||||
@ -1,138 +0,0 @@
@@ -1,138 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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.util.Arrays; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.RequestEntity; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.http.converter.FormHttpMessageConverter; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; |
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.client.ResponseErrorHandler; |
||||
import org.springframework.web.client.RestClientException; |
||||
import org.springframework.web.client.RestOperations; |
||||
import org.springframework.web.client.RestTemplate; |
||||
|
||||
/** |
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient} for the |
||||
* {@link AuthorizationGrantType#AUTHORIZATION_CODE authorization_code} grant. This |
||||
* implementation uses a {@link RestOperations} when requesting an access token credential |
||||
* at the Authorization Server's Token Endpoint. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.1 |
||||
* @see OAuth2AccessTokenResponseClient |
||||
* @see OAuth2AuthorizationCodeGrantRequest |
||||
* @see OAuth2AccessTokenResponse |
||||
* @see <a target="_blank" href= |
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request |
||||
* (Authorization Code Grant)</a> |
||||
* @see <a target="_blank" href= |
||||
* "https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response |
||||
* (Authorization Code Grant)</a> |
||||
* @deprecated Use {@link RestClientAuthorizationCodeTokenResponseClient} instead |
||||
*/ |
||||
@Deprecated(since = "6.4", forRemoval = true) |
||||
public final class DefaultAuthorizationCodeTokenResponseClient |
||||
implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> { |
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; |
||||
|
||||
private Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> requestEntityConverter = new ClientAuthenticationMethodValidatingRequestEntityConverter<>( |
||||
new OAuth2AuthorizationCodeGrantRequestEntityConverter()); |
||||
|
||||
private RestOperations restOperations; |
||||
|
||||
public DefaultAuthorizationCodeTokenResponseClient() { |
||||
RestTemplate restTemplate = new RestTemplate( |
||||
Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); |
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); |
||||
this.restOperations = restTemplate; |
||||
} |
||||
|
||||
@Override |
||||
public OAuth2AccessTokenResponse getTokenResponse( |
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) { |
||||
Assert.notNull(authorizationCodeGrantRequest, "authorizationCodeGrantRequest cannot be null"); |
||||
RequestEntity<?> request = this.requestEntityConverter.convert(authorizationCodeGrantRequest); |
||||
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request); |
||||
// As per spec, in Section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then we assume all requested scopes were
|
||||
// granted.
|
||||
// However, we use the explicit scopes returned in the response (if any).
|
||||
OAuth2AccessTokenResponse tokenResponse = response.getBody(); |
||||
Assert.notNull(tokenResponse, |
||||
"The authorization server responded to this Authorization Code grant request with an empty body; as such, it cannot be materialized into an OAuth2AccessTokenResponse instance. Please check the HTTP response code in your server logs for more details."); |
||||
return tokenResponse; |
||||
} |
||||
|
||||
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) { |
||||
try { |
||||
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class); |
||||
} |
||||
catch (RestClientException ex) { |
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, |
||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " |
||||
+ ex.getMessage(), |
||||
null); |
||||
throw new OAuth2AuthorizationException(oauth2Error, ex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link Converter} used for converting the |
||||
* {@link OAuth2AuthorizationCodeGrantRequest} to a {@link RequestEntity} |
||||
* representation of the OAuth 2.0 Access Token Request. |
||||
* @param requestEntityConverter the {@link Converter} used for converting to a |
||||
* {@link RequestEntity} representation of the Access Token Request |
||||
*/ |
||||
public void setRequestEntityConverter( |
||||
Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> requestEntityConverter) { |
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null"); |
||||
this.requestEntityConverter = requestEntityConverter; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token |
||||
* Response. |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured |
||||
* with the following: |
||||
* <ol> |
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and |
||||
* {@link OAuth2AccessTokenResponseHttpMessageConverter}</li> |
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li> |
||||
* </ol> |
||||
* @param restOperations the {@link RestOperations} used when requesting the Access |
||||
* Token Response |
||||
*/ |
||||
public void setRestOperations(RestOperations restOperations) { |
||||
Assert.notNull(restOperations, "restOperations cannot be null"); |
||||
this.restOperations = restOperations; |
||||
} |
||||
|
||||
} |
||||
@ -1,135 +0,0 @@
@@ -1,135 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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.util.Arrays; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.RequestEntity; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.http.converter.FormHttpMessageConverter; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; |
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.client.ResponseErrorHandler; |
||||
import org.springframework.web.client.RestClientException; |
||||
import org.springframework.web.client.RestOperations; |
||||
import org.springframework.web.client.RestTemplate; |
||||
|
||||
/** |
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient} for the |
||||
* {@link AuthorizationGrantType#CLIENT_CREDENTIALS client_credentials} grant. This |
||||
* implementation uses a {@link RestOperations} when requesting an access token credential |
||||
* at the Authorization Server's Token Endpoint. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.1 |
||||
* @see OAuth2AccessTokenResponseClient |
||||
* @see OAuth2ClientCredentialsGrantRequest |
||||
* @see OAuth2AccessTokenResponse |
||||
* @see <a target="_blank" href= |
||||
* "https://tools.ietf.org/html/rfc6749#section-4.4.2">Section 4.4.2 Access Token Request |
||||
* (Client Credentials Grant)</a> |
||||
* @see <a target="_blank" href= |
||||
* "https://tools.ietf.org/html/rfc6749#section-4.4.3">Section 4.4.3 Access Token Response |
||||
* (Client Credentials Grant)</a> |
||||
* @deprecated Use {@link RestClientClientCredentialsTokenResponseClient} instead |
||||
*/ |
||||
@Deprecated(since = "6.4", forRemoval = true) |
||||
public final class DefaultClientCredentialsTokenResponseClient |
||||
implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> { |
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; |
||||
|
||||
private Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> requestEntityConverter = new ClientAuthenticationMethodValidatingRequestEntityConverter<>( |
||||
new OAuth2ClientCredentialsGrantRequestEntityConverter()); |
||||
|
||||
private RestOperations restOperations; |
||||
|
||||
public DefaultClientCredentialsTokenResponseClient() { |
||||
RestTemplate restTemplate = new RestTemplate( |
||||
Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); |
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); |
||||
this.restOperations = restTemplate; |
||||
} |
||||
|
||||
@Override |
||||
public OAuth2AccessTokenResponse getTokenResponse( |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) { |
||||
Assert.notNull(clientCredentialsGrantRequest, "clientCredentialsGrantRequest cannot be null"); |
||||
RequestEntity<?> request = this.requestEntityConverter.convert(clientCredentialsGrantRequest); |
||||
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request); |
||||
// As per spec, in Section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then we assume all requested scopes were
|
||||
// granted.
|
||||
// However, we use the explicit scopes returned in the response (if any).
|
||||
return response.getBody(); |
||||
} |
||||
|
||||
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) { |
||||
try { |
||||
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class); |
||||
} |
||||
catch (RestClientException ex) { |
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, |
||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " |
||||
+ ex.getMessage(), |
||||
null); |
||||
throw new OAuth2AuthorizationException(oauth2Error, ex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link Converter} used for converting the |
||||
* {@link OAuth2ClientCredentialsGrantRequest} to a {@link RequestEntity} |
||||
* representation of the OAuth 2.0 Access Token Request. |
||||
* @param requestEntityConverter the {@link Converter} used for converting to a |
||||
* {@link RequestEntity} representation of the Access Token Request |
||||
*/ |
||||
public void setRequestEntityConverter( |
||||
Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> requestEntityConverter) { |
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null"); |
||||
this.requestEntityConverter = requestEntityConverter; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token |
||||
* Response. |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured |
||||
* with the following: |
||||
* <ol> |
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and |
||||
* {@link OAuth2AccessTokenResponseHttpMessageConverter}</li> |
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li> |
||||
* </ol> |
||||
* @param restOperations the {@link RestOperations} used when requesting the Access |
||||
* Token Response |
||||
*/ |
||||
public void setRestOperations(RestOperations restOperations) { |
||||
Assert.notNull(restOperations, "restOperations cannot be null"); |
||||
this.restOperations = restOperations; |
||||
} |
||||
|
||||
} |
||||
@ -1,130 +0,0 @@
@@ -1,130 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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.util.Arrays; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.RequestEntity; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.http.converter.FormHttpMessageConverter; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; |
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.client.ResponseErrorHandler; |
||||
import org.springframework.web.client.RestClientException; |
||||
import org.springframework.web.client.RestOperations; |
||||
import org.springframework.web.client.RestTemplate; |
||||
|
||||
/** |
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient} for the |
||||
* {@link AuthorizationGrantType#JWT_BEARER jwt-bearer} grant. This implementation uses a |
||||
* {@link RestOperations} when requesting an access token credential at the Authorization |
||||
* Server's Token Endpoint. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.5 |
||||
* @see OAuth2AccessTokenResponseClient |
||||
* @see JwtBearerGrantRequest |
||||
* @see OAuth2AccessTokenResponse |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7523#section-2.1">Section |
||||
* 2.1 Using JWTs as Authorization Grants</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7521#section-4.1">Section |
||||
* 4.1 Using Assertions as Authorization Grants</a> |
||||
* @deprecated Use {@link RestClientJwtBearerTokenResponseClient} instead |
||||
*/ |
||||
@Deprecated(since = "6.4", forRemoval = true) |
||||
public final class DefaultJwtBearerTokenResponseClient |
||||
implements OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> { |
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; |
||||
|
||||
private Converter<JwtBearerGrantRequest, RequestEntity<?>> requestEntityConverter = new ClientAuthenticationMethodValidatingRequestEntityConverter<>( |
||||
new JwtBearerGrantRequestEntityConverter()); |
||||
|
||||
private RestOperations restOperations; |
||||
|
||||
public DefaultJwtBearerTokenResponseClient() { |
||||
RestTemplate restTemplate = new RestTemplate( |
||||
Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); |
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); |
||||
this.restOperations = restTemplate; |
||||
} |
||||
|
||||
@Override |
||||
public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest jwtBearerGrantRequest) { |
||||
Assert.notNull(jwtBearerGrantRequest, "jwtBearerGrantRequest cannot be null"); |
||||
RequestEntity<?> request = this.requestEntityConverter.convert(jwtBearerGrantRequest); |
||||
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request); |
||||
// As per spec, in Section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then we assume all requested scopes were
|
||||
// granted.
|
||||
// However, we use the explicit scopes returned in the response (if any).
|
||||
return response.getBody(); |
||||
} |
||||
|
||||
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) { |
||||
try { |
||||
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class); |
||||
} |
||||
catch (RestClientException ex) { |
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, |
||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " |
||||
+ ex.getMessage(), |
||||
null); |
||||
throw new OAuth2AuthorizationException(oauth2Error, ex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link Converter} used for converting the {@link JwtBearerGrantRequest} to |
||||
* a {@link RequestEntity} representation of the OAuth 2.0 Access Token Request. |
||||
* @param requestEntityConverter the {@link Converter} used for converting to a |
||||
* {@link RequestEntity} representation of the Access Token Request |
||||
*/ |
||||
public void setRequestEntityConverter(Converter<JwtBearerGrantRequest, RequestEntity<?>> requestEntityConverter) { |
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null"); |
||||
this.requestEntityConverter = requestEntityConverter; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token |
||||
* Response. |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured |
||||
* with the following: |
||||
* <ol> |
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and |
||||
* {@link OAuth2AccessTokenResponseHttpMessageConverter}</li> |
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li> |
||||
* </ol> |
||||
* @param restOperations the {@link RestOperations} used when requesting the Access |
||||
* Token Response |
||||
*/ |
||||
public void setRestOperations(RestOperations restOperations) { |
||||
Assert.notNull(restOperations, "restOperations cannot be null"); |
||||
this.restOperations = restOperations; |
||||
} |
||||
|
||||
} |
||||
@ -1,144 +0,0 @@
@@ -1,144 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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.util.Arrays; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.RequestEntity; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.http.converter.FormHttpMessageConverter; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; |
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.web.client.ResponseErrorHandler; |
||||
import org.springframework.web.client.RestClientException; |
||||
import org.springframework.web.client.RestOperations; |
||||
import org.springframework.web.client.RestTemplate; |
||||
|
||||
/** |
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient} for the |
||||
* {@link AuthorizationGrantType#REFRESH_TOKEN refresh_token} grant. This implementation |
||||
* uses a {@link RestOperations} when requesting an access token credential at the |
||||
* Authorization Server's Token Endpoint. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.2 |
||||
* @see OAuth2AccessTokenResponseClient |
||||
* @see OAuth2RefreshTokenGrantRequest |
||||
* @see OAuth2AccessTokenResponse |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-6">Section 6 |
||||
* Refreshing an Access Token</a> |
||||
* @deprecated Use {@link RestClientRefreshTokenTokenResponseClient} instead |
||||
*/ |
||||
@Deprecated(since = "6.4", forRemoval = true) |
||||
public final class DefaultRefreshTokenTokenResponseClient |
||||
implements OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> { |
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; |
||||
|
||||
private Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>> requestEntityConverter = new ClientAuthenticationMethodValidatingRequestEntityConverter<>( |
||||
new OAuth2RefreshTokenGrantRequestEntityConverter()); |
||||
|
||||
private RestOperations restOperations; |
||||
|
||||
public DefaultRefreshTokenTokenResponseClient() { |
||||
RestTemplate restTemplate = new RestTemplate( |
||||
Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); |
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); |
||||
this.restOperations = restTemplate; |
||||
} |
||||
|
||||
@Override |
||||
public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) { |
||||
Assert.notNull(refreshTokenGrantRequest, "refreshTokenGrantRequest cannot be null"); |
||||
RequestEntity<?> request = this.requestEntityConverter.convert(refreshTokenGrantRequest); |
||||
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request); |
||||
OAuth2AccessTokenResponse tokenResponse = response.getBody(); |
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes()) |
||||
|| tokenResponse.getRefreshToken() == null) { |
||||
OAuth2AccessTokenResponse.Builder tokenResponseBuilder = OAuth2AccessTokenResponse |
||||
.withResponse(tokenResponse); |
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) { |
||||
// As per spec, in Section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then default to the scope
|
||||
// originally requested by the client in the Token Request
|
||||
tokenResponseBuilder.scopes(refreshTokenGrantRequest.getAccessToken().getScopes()); |
||||
} |
||||
if (tokenResponse.getRefreshToken() == null) { |
||||
// Reuse existing refresh token
|
||||
tokenResponseBuilder.refreshToken(refreshTokenGrantRequest.getRefreshToken().getTokenValue()); |
||||
} |
||||
tokenResponse = tokenResponseBuilder.build(); |
||||
} |
||||
return tokenResponse; |
||||
} |
||||
|
||||
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) { |
||||
try { |
||||
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class); |
||||
} |
||||
catch (RestClientException ex) { |
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, |
||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " |
||||
+ ex.getMessage(), |
||||
null); |
||||
throw new OAuth2AuthorizationException(oauth2Error, ex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link Converter} used for converting the |
||||
* {@link OAuth2RefreshTokenGrantRequest} to a {@link RequestEntity} representation of |
||||
* the OAuth 2.0 Access Token Request. |
||||
* @param requestEntityConverter the {@link Converter} used for converting to a |
||||
* {@link RequestEntity} representation of the Access Token Request |
||||
*/ |
||||
public void setRequestEntityConverter( |
||||
Converter<OAuth2RefreshTokenGrantRequest, RequestEntity<?>> requestEntityConverter) { |
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null"); |
||||
this.requestEntityConverter = requestEntityConverter; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token |
||||
* Response. |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured |
||||
* with the following: |
||||
* <ol> |
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and |
||||
* {@link OAuth2AccessTokenResponseHttpMessageConverter}</li> |
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li> |
||||
* </ol> |
||||
* @param restOperations the {@link RestOperations} used when requesting the Access |
||||
* Token Response |
||||
*/ |
||||
public void setRestOperations(RestOperations restOperations) { |
||||
Assert.notNull(restOperations, "restOperations cannot be null"); |
||||
this.restOperations = restOperations; |
||||
} |
||||
|
||||
} |
||||
@ -1,128 +0,0 @@
@@ -1,128 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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.util.Arrays; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.RequestEntity; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.http.converter.FormHttpMessageConverter; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; |
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.client.ResponseErrorHandler; |
||||
import org.springframework.web.client.RestClientException; |
||||
import org.springframework.web.client.RestOperations; |
||||
import org.springframework.web.client.RestTemplate; |
||||
|
||||
/** |
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient} for the |
||||
* {@link AuthorizationGrantType#TOKEN_EXCHANGE token-exchange} grant. This implementation |
||||
* uses a {@link RestOperations} when requesting an access token credential at the |
||||
* Authorization Server's Token Endpoint. |
||||
* |
||||
* @author Steve Riesenberg |
||||
* @since 6.3 |
||||
* @see OAuth2AccessTokenResponseClient |
||||
* @see TokenExchangeGrantRequest |
||||
* @see OAuth2AccessTokenResponse |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8693#section-2.1">Section |
||||
* 2.1 Request</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8693#section-2.2">Section |
||||
* 2.2 Response</a> |
||||
* @deprecated Use {@link RestClientRefreshTokenTokenResponseClient} instead |
||||
*/ |
||||
@Deprecated(since = "6.4", forRemoval = true) |
||||
public final class DefaultTokenExchangeTokenResponseClient |
||||
implements OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> { |
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; |
||||
|
||||
private Converter<TokenExchangeGrantRequest, RequestEntity<?>> requestEntityConverter = new ClientAuthenticationMethodValidatingRequestEntityConverter<>( |
||||
new TokenExchangeGrantRequestEntityConverter()); |
||||
|
||||
private RestOperations restOperations; |
||||
|
||||
public DefaultTokenExchangeTokenResponseClient() { |
||||
RestTemplate restTemplate = new RestTemplate( |
||||
Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); |
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); |
||||
this.restOperations = restTemplate; |
||||
} |
||||
|
||||
@Override |
||||
public OAuth2AccessTokenResponse getTokenResponse(TokenExchangeGrantRequest grantRequest) { |
||||
Assert.notNull(grantRequest, "grantRequest cannot be null"); |
||||
RequestEntity<?> requestEntity = this.requestEntityConverter.convert(grantRequest); |
||||
ResponseEntity<OAuth2AccessTokenResponse> responseEntity = getResponse(requestEntity); |
||||
|
||||
return responseEntity.getBody(); |
||||
} |
||||
|
||||
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) { |
||||
try { |
||||
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class); |
||||
} |
||||
catch (RestClientException ex) { |
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE, |
||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: " |
||||
+ ex.getMessage(), |
||||
null); |
||||
throw new OAuth2AuthorizationException(oauth2Error, ex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link Converter} used for converting the |
||||
* {@link TokenExchangeGrantRequest} to a {@link RequestEntity} representation of the |
||||
* OAuth 2.0 Access Token Request. |
||||
* @param requestEntityConverter the {@link Converter} used for converting to a |
||||
* {@link RequestEntity} representation of the Access Token Request |
||||
*/ |
||||
public void setRequestEntityConverter( |
||||
Converter<TokenExchangeGrantRequest, RequestEntity<?>> requestEntityConverter) { |
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null"); |
||||
this.requestEntityConverter = requestEntityConverter; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token |
||||
* Response. |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured |
||||
* with the following: |
||||
* <ol> |
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and |
||||
* {@link OAuth2AccessTokenResponseHttpMessageConverter}</li> |
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li> |
||||
* </ol> |
||||
* @param restOperations the {@link RestOperations} used when requesting the Access |
||||
* Token Response |
||||
*/ |
||||
public void setRestOperations(RestOperations restOperations) { |
||||
Assert.notNull(restOperations, "restOperations cannot be null"); |
||||
this.restOperations = restOperations; |
||||
} |
||||
|
||||
} |
||||
@ -1,63 +0,0 @@
@@ -1,63 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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 org.springframework.http.RequestEntity; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* An implementation of an {@link AbstractOAuth2AuthorizationGrantRequestEntityConverter} |
||||
* that converts the provided {@link JwtBearerGrantRequest} to a {@link RequestEntity} |
||||
* representation of an OAuth 2.0 Access Token Request for the JWT Bearer Grant. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.5 |
||||
* @see AbstractOAuth2AuthorizationGrantRequestEntityConverter |
||||
* @see JwtBearerGrantRequest |
||||
* @see RequestEntity |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7523#section-2.1">Section |
||||
* 2.1 Using JWTs as Authorization Grants</a> |
||||
* @deprecated Use {@link DefaultOAuth2TokenRequestParametersConverter} instead |
||||
*/ |
||||
@Deprecated(since = "6.4", forRemoval = true) |
||||
public class JwtBearerGrantRequestEntityConverter |
||||
extends AbstractOAuth2AuthorizationGrantRequestEntityConverter<JwtBearerGrantRequest> { |
||||
|
||||
@Override |
||||
protected MultiValueMap<String, String> createParameters(JwtBearerGrantRequest jwtBearerGrantRequest) { |
||||
ClientRegistration clientRegistration = jwtBearerGrantRequest.getClientRegistration(); |
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
||||
parameters.add(OAuth2ParameterNames.GRANT_TYPE, jwtBearerGrantRequest.getGrantType().getValue()); |
||||
parameters.add(OAuth2ParameterNames.ASSERTION, jwtBearerGrantRequest.getJwt().getTokenValue()); |
||||
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) { |
||||
parameters.add(OAuth2ParameterNames.SCOPE, |
||||
StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); |
||||
} |
||||
if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientRegistration.getClientAuthenticationMethod())) { |
||||
parameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId()); |
||||
parameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret()); |
||||
} |
||||
return parameters; |
||||
} |
||||
|
||||
} |
||||
@ -1,72 +0,0 @@
@@ -1,72 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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 org.springframework.http.RequestEntity; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
/** |
||||
* An implementation of an {@link AbstractOAuth2AuthorizationGrantRequestEntityConverter} |
||||
* that converts the provided {@link OAuth2AuthorizationCodeGrantRequest} to a |
||||
* {@link RequestEntity} representation of an OAuth 2.0 Access Token Request for the |
||||
* Authorization Code Grant. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.1 |
||||
* @see AbstractOAuth2AuthorizationGrantRequestEntityConverter |
||||
* @see OAuth2AuthorizationCodeGrantRequest |
||||
* @see RequestEntity |
||||
* @deprecated Use {@link DefaultOAuth2TokenRequestParametersConverter} instead |
||||
*/ |
||||
@Deprecated(since = "6.4", forRemoval = true) |
||||
public class OAuth2AuthorizationCodeGrantRequestEntityConverter |
||||
extends AbstractOAuth2AuthorizationGrantRequestEntityConverter<OAuth2AuthorizationCodeGrantRequest> { |
||||
|
||||
@Override |
||||
protected MultiValueMap<String, String> createParameters( |
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) { |
||||
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration(); |
||||
OAuth2AuthorizationExchange authorizationExchange = authorizationCodeGrantRequest.getAuthorizationExchange(); |
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
||||
parameters.add(OAuth2ParameterNames.GRANT_TYPE, authorizationCodeGrantRequest.getGrantType().getValue()); |
||||
parameters.add(OAuth2ParameterNames.CODE, authorizationExchange.getAuthorizationResponse().getCode()); |
||||
String redirectUri = authorizationExchange.getAuthorizationRequest().getRedirectUri(); |
||||
String codeVerifier = authorizationExchange.getAuthorizationRequest() |
||||
.getAttribute(PkceParameterNames.CODE_VERIFIER); |
||||
if (redirectUri != null) { |
||||
parameters.add(OAuth2ParameterNames.REDIRECT_URI, redirectUri); |
||||
} |
||||
if (!ClientAuthenticationMethod.CLIENT_SECRET_BASIC |
||||
.equals(clientRegistration.getClientAuthenticationMethod())) { |
||||
parameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId()); |
||||
} |
||||
if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientRegistration.getClientAuthenticationMethod())) { |
||||
parameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret()); |
||||
} |
||||
if (codeVerifier != null) { |
||||
parameters.add(PkceParameterNames.CODE_VERIFIER, codeVerifier); |
||||
} |
||||
return parameters; |
||||
} |
||||
|
||||
} |
||||
@ -1,62 +0,0 @@
@@ -1,62 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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 org.springframework.http.RequestEntity; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* An implementation of an {@link AbstractOAuth2AuthorizationGrantRequestEntityConverter} |
||||
* that converts the provided {@link OAuth2ClientCredentialsGrantRequest} to a |
||||
* {@link RequestEntity} representation of an OAuth 2.0 Access Token Request for the |
||||
* Client Credentials Grant. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.1 |
||||
* @see AbstractOAuth2AuthorizationGrantRequestEntityConverter |
||||
* @see OAuth2ClientCredentialsGrantRequest |
||||
* @see RequestEntity |
||||
* @deprecated Use {@link DefaultOAuth2TokenRequestParametersConverter} instead |
||||
*/ |
||||
@Deprecated(since = "6.4", forRemoval = true) |
||||
public class OAuth2ClientCredentialsGrantRequestEntityConverter |
||||
extends AbstractOAuth2AuthorizationGrantRequestEntityConverter<OAuth2ClientCredentialsGrantRequest> { |
||||
|
||||
@Override |
||||
protected MultiValueMap<String, String> createParameters( |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) { |
||||
ClientRegistration clientRegistration = clientCredentialsGrantRequest.getClientRegistration(); |
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
||||
parameters.add(OAuth2ParameterNames.GRANT_TYPE, clientCredentialsGrantRequest.getGrantType().getValue()); |
||||
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) { |
||||
parameters.add(OAuth2ParameterNames.SCOPE, |
||||
StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); |
||||
} |
||||
if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientRegistration.getClientAuthenticationMethod())) { |
||||
parameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId()); |
||||
parameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret()); |
||||
} |
||||
return parameters; |
||||
} |
||||
|
||||
} |
||||
@ -1,62 +0,0 @@
@@ -1,62 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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 org.springframework.http.RequestEntity; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* An implementation of an {@link AbstractOAuth2AuthorizationGrantRequestEntityConverter} |
||||
* that converts the provided {@link OAuth2RefreshTokenGrantRequest} to a |
||||
* {@link RequestEntity} representation of an OAuth 2.0 Access Token Request for the |
||||
* Refresh Token Grant. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 5.2 |
||||
* @see AbstractOAuth2AuthorizationGrantRequestEntityConverter |
||||
* @see OAuth2RefreshTokenGrantRequest |
||||
* @see RequestEntity |
||||
* @deprecated Use {@link DefaultOAuth2TokenRequestParametersConverter} instead |
||||
*/ |
||||
@Deprecated(since = "6.4", forRemoval = true) |
||||
public class OAuth2RefreshTokenGrantRequestEntityConverter |
||||
extends AbstractOAuth2AuthorizationGrantRequestEntityConverter<OAuth2RefreshTokenGrantRequest> { |
||||
|
||||
@Override |
||||
protected MultiValueMap<String, String> createParameters(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) { |
||||
ClientRegistration clientRegistration = refreshTokenGrantRequest.getClientRegistration(); |
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
||||
parameters.add(OAuth2ParameterNames.GRANT_TYPE, refreshTokenGrantRequest.getGrantType().getValue()); |
||||
parameters.add(OAuth2ParameterNames.REFRESH_TOKEN, refreshTokenGrantRequest.getRefreshToken().getTokenValue()); |
||||
if (!CollectionUtils.isEmpty(refreshTokenGrantRequest.getScopes())) { |
||||
parameters.add(OAuth2ParameterNames.SCOPE, |
||||
StringUtils.collectionToDelimitedString(refreshTokenGrantRequest.getScopes(), " ")); |
||||
} |
||||
if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientRegistration.getClientAuthenticationMethod())) { |
||||
parameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId()); |
||||
parameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret()); |
||||
} |
||||
return parameters; |
||||
} |
||||
|
||||
} |
||||
@ -1,81 +0,0 @@
@@ -1,81 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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 org.springframework.http.RequestEntity; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.security.oauth2.core.OAuth2Token; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.security.oauth2.jwt.Jwt; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* An implementation of an {@link AbstractOAuth2AuthorizationGrantRequestEntityConverter} |
||||
* that converts the provided {@link TokenExchangeGrantRequest} to a {@link RequestEntity} |
||||
* representation of an OAuth 2.0 Access Token Request for the Token Exchange Grant. |
||||
* |
||||
* @author Steve Riesenberg |
||||
* @since 6.3 |
||||
* @see AbstractOAuth2AuthorizationGrantRequestEntityConverter |
||||
* @see TokenExchangeGrantRequest |
||||
* @see RequestEntity |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8693#section-1.1">Section |
||||
* 1.1 Delegation vs. Impersonation Semantics</a> |
||||
* @deprecated Use {@link DefaultOAuth2TokenRequestParametersConverter} instead |
||||
*/ |
||||
@Deprecated(since = "6.4", forRemoval = true) |
||||
public class TokenExchangeGrantRequestEntityConverter |
||||
extends AbstractOAuth2AuthorizationGrantRequestEntityConverter<TokenExchangeGrantRequest> { |
||||
|
||||
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; |
||||
|
||||
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; |
||||
|
||||
@Override |
||||
protected MultiValueMap<String, String> createParameters(TokenExchangeGrantRequest grantRequest) { |
||||
ClientRegistration clientRegistration = grantRequest.getClientRegistration(); |
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
||||
parameters.add(OAuth2ParameterNames.GRANT_TYPE, grantRequest.getGrantType().getValue()); |
||||
parameters.add(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||
OAuth2Token subjectToken = grantRequest.getSubjectToken(); |
||||
parameters.add(OAuth2ParameterNames.SUBJECT_TOKEN, subjectToken.getTokenValue()); |
||||
parameters.add(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, tokenType(subjectToken)); |
||||
OAuth2Token actorToken = grantRequest.getActorToken(); |
||||
if (actorToken != null) { |
||||
parameters.add(OAuth2ParameterNames.ACTOR_TOKEN, actorToken.getTokenValue()); |
||||
parameters.add(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, tokenType(actorToken)); |
||||
} |
||||
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) { |
||||
parameters.add(OAuth2ParameterNames.SCOPE, |
||||
StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); |
||||
} |
||||
if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientRegistration.getClientAuthenticationMethod())) { |
||||
parameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId()); |
||||
parameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret()); |
||||
} |
||||
return parameters; |
||||
} |
||||
|
||||
private static String tokenType(OAuth2Token token) { |
||||
return (token instanceof Jwt) ? JWT_TOKEN_TYPE_VALUE : ACCESS_TOKEN_TYPE_VALUE; |
||||
} |
||||
|
||||
} |
||||
@ -1,422 +0,0 @@
@@ -1,422 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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 java.time.Instant; |
||||
import java.util.function.Function; |
||||
|
||||
import javax.crypto.spec.SecretKeySpec; |
||||
|
||||
import com.nimbusds.jose.jwk.JWK; |
||||
import okhttp3.mockwebserver.MockResponse; |
||||
import okhttp3.mockwebserver.MockWebServer; |
||||
import okhttp3.mockwebserver.RecordedRequest; |
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; |
||||
import org.springframework.security.oauth2.jose.TestJwks; |
||||
import org.springframework.security.oauth2.jose.TestKeys; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link DefaultAuthorizationCodeTokenResponseClient}. |
||||
* |
||||
* @author Joe Grandja |
||||
*/ |
||||
public class DefaultAuthorizationCodeTokenResponseClientTests { |
||||
|
||||
private DefaultAuthorizationCodeTokenResponseClient tokenResponseClient; |
||||
|
||||
private ClientRegistration.Builder clientRegistration; |
||||
|
||||
private MockWebServer server; |
||||
|
||||
@BeforeEach |
||||
public void setup() throws Exception { |
||||
this.tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); |
||||
this.server = new MockWebServer(); |
||||
this.server.start(); |
||||
String tokenUri = this.server.url("/oauth2/token").toString(); |
||||
// @formatter:off
|
||||
this.clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.clientId("client-1") |
||||
.clientSecret("secret") |
||||
.redirectUri("https://client.com/callback/client-1") |
||||
.tokenUri(tokenUri) |
||||
.scope("read", "write"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@AfterEach |
||||
public void cleanup() throws Exception { |
||||
this.server.shutdown(); |
||||
} |
||||
|
||||
@Test |
||||
public void setRequestEntityConverterWhenConverterIsNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setRequestEntityConverter(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void setRestOperationsWhenRestOperationsIsNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setRestOperations(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenRequestIsNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.getTokenResponse(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read write\",\n" |
||||
+ " \"refresh_token\": \"refresh-token-1234\",\n" |
||||
+ " \"custom_parameter_1\": \"custom-value-1\",\n" |
||||
+ " \"custom_parameter_2\": \"custom-value-2\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(authorizationCodeGrantRequest(this.clientRegistration.build())); |
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) |
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters).contains("grant_type=authorization_code"); |
||||
assertThat(formParameters).contains("code=code-1234"); |
||||
assertThat(formParameters).contains("redirect_uri=https%3A%2F%2Fclient.com%2Fcallback%2Fclient-1"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); |
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read", "write"); |
||||
assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); |
||||
assertThat(accessTokenResponse.getAdditionalParameters()).hasSize(2); |
||||
assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); |
||||
assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretBasicThenAuthorizationHeaderIsSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
this.tokenResponseClient.getTokenResponse(authorizationCodeGrantRequest(this.clientRegistration.build())); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretPostThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) |
||||
.build(); |
||||
this.tokenResponseClient.getTokenResponse(authorizationCodeGrantRequest(clientRegistration)); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters).contains("client_id=client-1"); |
||||
assertThat(formParameters).contains("client_secret=secret"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretJwtThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) |
||||
.clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY) |
||||
.build(); |
||||
// @formatter:on
|
||||
|
||||
// Configure Jwt client authentication converter
|
||||
SecretKeySpec secretKey = new SecretKeySpec( |
||||
clientRegistration.getClientSecret().getBytes(StandardCharsets.UTF_8), "HmacSHA256"); |
||||
JWK jwk = TestJwks.jwk(secretKey).build(); |
||||
Function<ClientRegistration, JWK> jwkResolver = (registration) -> jwk; |
||||
configureJwtClientAuthenticationConverter(jwkResolver); |
||||
|
||||
this.tokenResponseClient.getTokenResponse(authorizationCodeGrantRequest(clientRegistration)); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters) |
||||
.contains("client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer"); |
||||
assertThat(formParameters).contains("client_assertion="); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationPrivateKeyJwtThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT) |
||||
.build(); |
||||
// @formatter:on
|
||||
|
||||
// Configure Jwt client authentication converter
|
||||
JWK jwk = TestJwks.DEFAULT_RSA_JWK; |
||||
Function<ClientRegistration, JWK> jwkResolver = (registration) -> jwk; |
||||
configureJwtClientAuthenticationConverter(jwkResolver); |
||||
|
||||
this.tokenResponseClient.getTokenResponse(authorizationCodeGrantRequest(clientRegistration)); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters) |
||||
.contains("client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer"); |
||||
assertThat(formParameters).contains("client_assertion="); |
||||
} |
||||
|
||||
// gh-13143
|
||||
@Test |
||||
public void getTokenResponseWhenTokenEndpointReturnsEmptyBodyThenIllegalArgument() { |
||||
this.server.enqueue(new MockResponse().setResponseCode(302)); |
||||
ClientRegistration clientRegistration = this.clientRegistration.build(); |
||||
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy( |
||||
() -> this.tokenResponseClient.getTokenResponse(authorizationCodeGrantRequest(clientRegistration))); |
||||
} |
||||
|
||||
private void configureJwtClientAuthenticationConverter(Function<ClientRegistration, JWK> jwkResolver) { |
||||
NimbusJwtClientAuthenticationParametersConverter<OAuth2AuthorizationCodeGrantRequest> jwtClientAuthenticationConverter = new NimbusJwtClientAuthenticationParametersConverter<>( |
||||
jwkResolver); |
||||
OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter(); |
||||
requestEntityConverter.addParametersConverter(jwtClientAuthenticationConverter); |
||||
this.tokenResponseClient.setRequestEntityConverter(requestEntityConverter); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"not-bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient |
||||
.getTokenResponse(authorizationCodeGrantRequest(this.clientRegistration.build()))) |
||||
.withMessageContaining( |
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response") |
||||
.havingRootCause() |
||||
.withMessageContaining("tokenType cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseAndMissingTokenTypeParameterThenThrowOAuth2AuthorizationException() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient |
||||
.getTokenResponse(authorizationCodeGrantRequest(this.clientRegistration.build()))) |
||||
.withMessageContaining( |
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response") |
||||
.havingRootCause() |
||||
.withMessageContaining("tokenType cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasResponseScope() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"refresh_token\": \"refresh-token-1234\",\n" |
||||
+ " \"scope\": \"read\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(authorizationCodeGrantRequest(this.clientRegistration.build())); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"refresh_token\": \"refresh-token-1234\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(authorizationCodeGrantRequest(this.clientRegistration.build())); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenTokenUriInvalidThenThrowOAuth2AuthorizationException() { |
||||
String invalidTokenUri = "https://invalid-provider.com/oauth2/token"; |
||||
ClientRegistration clientRegistration = this.clientRegistration.tokenUri(invalidTokenUri).build(); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy( |
||||
() -> this.tokenResponseClient.getTokenResponse(authorizationCodeGrantRequest(clientRegistration))) |
||||
.withMessageContaining( |
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenMalformedResponseThenThrowOAuth2AuthorizationException() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read write\",\n" |
||||
+ " \"refresh_token\": \"refresh-token-1234\",\n" |
||||
+ " \"custom_parameter_1\": \"custom-value-1\",\n" |
||||
+ " \"custom_parameter_2\": \"custom-value-2\"\n"; |
||||
// "}\n"; // Make the JSON invalid/malformed
|
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient |
||||
.getTokenResponse(authorizationCodeGrantRequest(this.clientRegistration.build()))) |
||||
.withMessageContaining( |
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() { |
||||
String accessTokenErrorResponse = "{\n" + " \"error\": \"unauthorized_client\"\n" + "}\n"; |
||||
this.server.enqueue(jsonResponse(accessTokenErrorResponse).setResponseCode(400)); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient |
||||
.getTokenResponse(authorizationCodeGrantRequest(this.clientRegistration.build()))) |
||||
.withMessageContaining("[unauthorized_client]"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationException() { |
||||
this.server.enqueue(new MockResponse().setResponseCode(500)); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient |
||||
.getTokenResponse(authorizationCodeGrantRequest(this.clientRegistration.build()))) |
||||
.withMessageContaining("[invalid_token_response] An error occurred while attempting to retrieve " |
||||
+ "the OAuth 2.0 Access Token Response"); |
||||
} |
||||
|
||||
// gh-13144
|
||||
@Test |
||||
public void getTokenResponseWhenCustomClientAuthenticationMethodThenIllegalArgument() { |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(new ClientAuthenticationMethod("basic")) |
||||
.build(); |
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest = authorizationCodeGrantRequest( |
||||
clientRegistration); |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(authorizationCodeGrantRequest)); |
||||
} |
||||
|
||||
// gh-13144
|
||||
@Test |
||||
public void getTokenResponseWhenUnsupportedClientAuthenticationMethodThenIllegalArgument() { |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) |
||||
.build(); |
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest = authorizationCodeGrantRequest( |
||||
clientRegistration); |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(authorizationCodeGrantRequest)); |
||||
} |
||||
|
||||
private OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest(ClientRegistration clientRegistration) { |
||||
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() |
||||
.clientId(clientRegistration.getClientId()) |
||||
.state("state-1234") |
||||
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()) |
||||
.redirectUri(clientRegistration.getRedirectUri()) |
||||
.scopes(clientRegistration.getScopes()) |
||||
.build(); |
||||
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponse.success("code-1234") |
||||
.state("state-1234") |
||||
.redirectUri(clientRegistration.getRedirectUri()) |
||||
.build(); |
||||
OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest, |
||||
authorizationResponse); |
||||
return new OAuth2AuthorizationCodeGrantRequest(clientRegistration, authorizationExchange); |
||||
} |
||||
|
||||
private MockResponse jsonResponse(String json) { |
||||
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json); |
||||
} |
||||
|
||||
} |
||||
@ -1,413 +0,0 @@
@@ -1,413 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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 java.time.Instant; |
||||
import java.util.function.Function; |
||||
|
||||
import javax.crypto.spec.SecretKeySpec; |
||||
|
||||
import com.nimbusds.jose.jwk.JWK; |
||||
import okhttp3.mockwebserver.MockResponse; |
||||
import okhttp3.mockwebserver.MockWebServer; |
||||
import okhttp3.mockwebserver.RecordedRequest; |
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||
import org.springframework.security.oauth2.jose.TestJwks; |
||||
import org.springframework.security.oauth2.jose.TestKeys; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link DefaultClientCredentialsTokenResponseClient}. |
||||
* |
||||
* @author Joe Grandja |
||||
*/ |
||||
public class DefaultClientCredentialsTokenResponseClientTests { |
||||
|
||||
private DefaultClientCredentialsTokenResponseClient tokenResponseClient; |
||||
|
||||
private ClientRegistration.Builder clientRegistration; |
||||
|
||||
private MockWebServer server; |
||||
|
||||
@BeforeEach |
||||
public void setup() throws Exception { |
||||
this.tokenResponseClient = new DefaultClientCredentialsTokenResponseClient(); |
||||
this.server = new MockWebServer(); |
||||
this.server.start(); |
||||
String tokenUri = this.server.url("/oauth2/token").toString(); |
||||
// @formatter:off
|
||||
this.clientRegistration = TestClientRegistrations.clientCredentials() |
||||
.clientId("client-1") |
||||
.clientSecret("secret") |
||||
.tokenUri(tokenUri) |
||||
.scope("read", "write"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@AfterEach |
||||
public void cleanup() throws Exception { |
||||
this.server.shutdown(); |
||||
} |
||||
|
||||
@Test |
||||
public void setRequestEntityConverterWhenConverterIsNullThenThrowIllegalArgumentException() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.tokenResponseClient.setRequestEntityConverter(null)); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void setRestOperationsWhenRestOperationsIsNullThenThrowIllegalArgumentException() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.tokenResponseClient.setRestOperations(null)); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenRequestIsNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.getTokenResponse(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read write\",\n" |
||||
+ " \"custom_parameter_1\": \"custom-value-1\",\n" |
||||
+ " \"custom_parameter_2\": \"custom-value-2\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
this.clientRegistration.build()); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(clientCredentialsGrantRequest); |
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) |
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters).contains("grant_type=client_credentials"); |
||||
assertThat(formParameters).contains("scope=read+write"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); |
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read", "write"); |
||||
assertThat(accessTokenResponse.getRefreshToken()).isNull(); |
||||
assertThat(accessTokenResponse.getAdditionalParameters()).hasSize(2); |
||||
assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); |
||||
assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretBasicThenAuthorizationHeaderIsSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
this.clientRegistration.build()); |
||||
this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretPostThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) |
||||
.build(); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
clientRegistration); |
||||
this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters).contains("client_id=client-1"); |
||||
assertThat(formParameters).contains("client_secret=secret"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretJwtThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) |
||||
.clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY) |
||||
.build(); |
||||
// @formatter:on
|
||||
|
||||
// Configure Jwt client authentication converter
|
||||
SecretKeySpec secretKey = new SecretKeySpec( |
||||
clientRegistration.getClientSecret().getBytes(StandardCharsets.UTF_8), "HmacSHA256"); |
||||
JWK jwk = TestJwks.jwk(secretKey).build(); |
||||
Function<ClientRegistration, JWK> jwkResolver = (registration) -> jwk; |
||||
configureJwtClientAuthenticationConverter(jwkResolver); |
||||
|
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
clientRegistration); |
||||
this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters) |
||||
.contains("client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer"); |
||||
assertThat(formParameters).contains("client_assertion="); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationPrivateKeyJwtThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT) |
||||
.build(); |
||||
// @formatter:on
|
||||
|
||||
// Configure Jwt client authentication converter
|
||||
JWK jwk = TestJwks.DEFAULT_RSA_JWK; |
||||
Function<ClientRegistration, JWK> jwkResolver = (registration) -> jwk; |
||||
configureJwtClientAuthenticationConverter(jwkResolver); |
||||
|
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
clientRegistration); |
||||
this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters) |
||||
.contains("client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer"); |
||||
assertThat(formParameters).contains("client_assertion="); |
||||
} |
||||
|
||||
private void configureJwtClientAuthenticationConverter(Function<ClientRegistration, JWK> jwkResolver) { |
||||
NimbusJwtClientAuthenticationParametersConverter<OAuth2ClientCredentialsGrantRequest> jwtClientAuthenticationConverter = new NimbusJwtClientAuthenticationParametersConverter<>( |
||||
jwkResolver); |
||||
OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter = new OAuth2ClientCredentialsGrantRequestEntityConverter(); |
||||
requestEntityConverter.addParametersConverter(jwtClientAuthenticationConverter); |
||||
this.tokenResponseClient.setRequestEntityConverter(requestEntityConverter); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"not-bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
this.clientRegistration.build()); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest)) |
||||
.withMessageContaining( |
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response") |
||||
.havingRootCause() |
||||
.withMessageContaining("tokenType cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseAndMissingTokenTypeParameterThenThrowOAuth2AuthorizationException() { |
||||
String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\"\n" + "}\n"; |
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
this.clientRegistration.build()); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest)) |
||||
.withMessageContaining( |
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response") |
||||
.havingRootCause() |
||||
.withMessageContaining("tokenType cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasResponseScope() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
this.clientRegistration.build()); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(clientCredentialsGrantRequest); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
this.clientRegistration.build()); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(clientCredentialsGrantRequest); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenTokenUriInvalidThenThrowOAuth2AuthorizationException() { |
||||
String invalidTokenUri = "https://invalid-provider.com/oauth2/token"; |
||||
ClientRegistration clientRegistration = this.clientRegistration.tokenUri(invalidTokenUri).build(); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
clientRegistration); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest)) |
||||
.withMessageContaining( |
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenMalformedResponseThenThrowOAuth2AuthorizationException() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read write\",\n" |
||||
+ " \"custom_parameter_1\": \"custom-value-1\",\n" |
||||
+ " \"custom_parameter_2\": \"custom-value-2\"\n"; |
||||
// "}\n"; // Make the JSON invalid/malformed
|
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
this.clientRegistration.build()); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest)) |
||||
.withMessageContaining( |
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() { |
||||
// @formatter:off
|
||||
String accessTokenErrorResponse = "{\n" |
||||
+ " \"error\": \"unauthorized_client\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenErrorResponse).setResponseCode(400)); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
this.clientRegistration.build()); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest)) |
||||
.withMessageContaining("[unauthorized_client]"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationException() { |
||||
this.server.enqueue(new MockResponse().setResponseCode(500)); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
this.clientRegistration.build()); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest)) |
||||
.withMessageContaining( |
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response"); |
||||
} |
||||
|
||||
// gh-13144
|
||||
@Test |
||||
public void getTokenResponseWhenCustomClientAuthenticationMethodThenIllegalArgument() { |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(new ClientAuthenticationMethod("basic")) |
||||
.build(); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
clientRegistration); |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest)); |
||||
} |
||||
|
||||
// gh-13144
|
||||
@Test |
||||
public void getTokenResponseWhenUnsupportedClientAuthenticationMethodThenIllegalArgument() { |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) |
||||
.build(); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
clientRegistration); |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(clientCredentialsGrantRequest)); |
||||
} |
||||
|
||||
private MockResponse jsonResponse(String json) { |
||||
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json); |
||||
} |
||||
|
||||
} |
||||
@ -1,274 +0,0 @@
@@ -1,274 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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.net.URLEncoder; |
||||
import java.time.Instant; |
||||
|
||||
import okhttp3.mockwebserver.MockResponse; |
||||
import okhttp3.mockwebserver.MockWebServer; |
||||
import okhttp3.mockwebserver.RecordedRequest; |
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.MediaType; |
||||
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.OAuth2AccessToken; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||
import org.springframework.security.oauth2.jwt.Jwt; |
||||
import org.springframework.security.oauth2.jwt.TestJwts; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link DefaultJwtBearerTokenResponseClient}. |
||||
* |
||||
* @author Hassene Laaribi |
||||
* @author Joe Grandja |
||||
*/ |
||||
public class DefaultJwtBearerTokenResponseClientTests { |
||||
|
||||
private DefaultJwtBearerTokenResponseClient tokenResponseClient; |
||||
|
||||
private ClientRegistration.Builder clientRegistration; |
||||
|
||||
private Jwt jwtAssertion; |
||||
|
||||
private MockWebServer server; |
||||
|
||||
@BeforeEach |
||||
public void setup() throws Exception { |
||||
this.tokenResponseClient = new DefaultJwtBearerTokenResponseClient(); |
||||
this.server = new MockWebServer(); |
||||
this.server.start(); |
||||
String tokenUri = this.server.url("/oauth2/token").toString(); |
||||
// @formatter:off
|
||||
this.clientRegistration = TestClientRegistrations.clientCredentials() |
||||
.clientId("client-1") |
||||
.clientSecret("secret") |
||||
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER) |
||||
.tokenUri(tokenUri) |
||||
.scope("read", "write"); |
||||
// @formatter:on
|
||||
this.jwtAssertion = TestJwts.jwt().build(); |
||||
} |
||||
|
||||
@AfterEach |
||||
public void cleanup() throws Exception { |
||||
this.server.shutdown(); |
||||
} |
||||
|
||||
@Test |
||||
public void setRequestEntityConverterWhenConverterIsNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setRequestEntityConverter(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void setRestOperationsWhenRestOperationsIsNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setRestOperations(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenRequestIsNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.getTokenResponse(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read write\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600); |
||||
ClientRegistration clientRegistration = this.clientRegistration.build(); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(clientRegistration, this.jwtAssertion); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(jwtBearerGrantRequest); |
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) |
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters) |
||||
.contains("grant_type=" + URLEncoder.encode(AuthorizationGrantType.JWT_BEARER.getValue(), "UTF-8")); |
||||
assertThat(formParameters).contains("scope=read+write"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); |
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); |
||||
assertThat(accessTokenResponse.getRefreshToken()).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretBasicThenAuthorizationHeaderIsSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(this.clientRegistration.build(), |
||||
this.jwtAssertion); |
||||
this.tokenResponseClient.getTokenResponse(jwtBearerGrantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretPostThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) |
||||
.build(); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(clientRegistration, this.jwtAssertion); |
||||
this.tokenResponseClient.getTokenResponse(jwtBearerGrantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters).contains("client_id=client-1"); |
||||
assertThat(formParameters).contains("client_secret=secret"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"not-bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(this.clientRegistration.build(), |
||||
this.jwtAssertion); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(jwtBearerGrantRequest)) |
||||
.withMessageContaining( |
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response") |
||||
.havingRootCause() |
||||
.withMessageContaining("tokenType cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasResponseScope() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(this.clientRegistration.build(), |
||||
this.jwtAssertion); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(jwtBearerGrantRequest); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(this.clientRegistration.build(), |
||||
this.jwtAssertion); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(jwtBearerGrantRequest); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() { |
||||
String accessTokenErrorResponse = "{\n" + " \"error\": \"invalid_grant\"\n" + "}\n"; |
||||
this.server.enqueue(jsonResponse(accessTokenErrorResponse).setResponseCode(400)); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(this.clientRegistration.build(), |
||||
this.jwtAssertion); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(jwtBearerGrantRequest)) |
||||
.withMessageContaining("[invalid_grant]"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationException() { |
||||
this.server.enqueue(new MockResponse().setResponseCode(500)); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(this.clientRegistration.build(), |
||||
this.jwtAssertion); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(jwtBearerGrantRequest)) |
||||
.withMessageContaining("[invalid_token_response] An error occurred while attempting to " |
||||
+ "retrieve the OAuth 2.0 Access Token Response"); |
||||
} |
||||
|
||||
// gh-13144
|
||||
@Test |
||||
public void getTokenResponseWhenCustomClientAuthenticationMethodThenIllegalArgument() { |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(new ClientAuthenticationMethod("basic")) |
||||
.build(); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(clientRegistration, this.jwtAssertion); |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(jwtBearerGrantRequest)); |
||||
} |
||||
|
||||
// gh-13144
|
||||
@Test |
||||
public void getTokenResponseWhenUnsupportedClientAuthenticationMethodThenIllegalArgument() { |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) |
||||
.build(); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(clientRegistration, this.jwtAssertion); |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(jwtBearerGrantRequest)); |
||||
} |
||||
|
||||
private MockResponse jsonResponse(String json) { |
||||
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json); |
||||
} |
||||
|
||||
} |
||||
@ -1,347 +0,0 @@
@@ -1,347 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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 java.time.Instant; |
||||
import java.util.Collections; |
||||
import java.util.function.Function; |
||||
|
||||
import javax.crypto.spec.SecretKeySpec; |
||||
|
||||
import com.nimbusds.jose.jwk.JWK; |
||||
import okhttp3.mockwebserver.MockResponse; |
||||
import okhttp3.mockwebserver.MockWebServer; |
||||
import okhttp3.mockwebserver.RecordedRequest; |
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations; |
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken; |
||||
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; |
||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||
import org.springframework.security.oauth2.jose.TestJwks; |
||||
import org.springframework.security.oauth2.jose.TestKeys; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link DefaultRefreshTokenTokenResponseClient}. |
||||
* |
||||
* @author Joe Grandja |
||||
*/ |
||||
public class DefaultRefreshTokenTokenResponseClientTests { |
||||
|
||||
private DefaultRefreshTokenTokenResponseClient tokenResponseClient; |
||||
|
||||
private ClientRegistration.Builder clientRegistration; |
||||
|
||||
private OAuth2AccessToken accessToken; |
||||
|
||||
private OAuth2RefreshToken refreshToken; |
||||
|
||||
private MockWebServer server; |
||||
|
||||
@BeforeEach |
||||
public void setup() throws Exception { |
||||
this.tokenResponseClient = new DefaultRefreshTokenTokenResponseClient(); |
||||
this.server = new MockWebServer(); |
||||
this.server.start(); |
||||
String tokenUri = this.server.url("/oauth2/token").toString(); |
||||
this.clientRegistration = TestClientRegistrations.clientRegistration().tokenUri(tokenUri); |
||||
this.accessToken = TestOAuth2AccessTokens.scopes("read", "write"); |
||||
this.refreshToken = TestOAuth2RefreshTokens.refreshToken(); |
||||
} |
||||
|
||||
@AfterEach |
||||
public void cleanup() throws Exception { |
||||
this.server.shutdown(); |
||||
} |
||||
|
||||
@Test |
||||
public void setRequestEntityConverterWhenConverterIsNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setRequestEntityConverter(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void setRestOperationsWhenRestOperationsIsNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setRestOperations(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenRequestIsNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.getTokenResponse(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read write\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest( |
||||
this.clientRegistration.build(), this.accessToken, this.refreshToken); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(refreshTokenGrantRequest); |
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) |
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters).contains("grant_type=refresh_token"); |
||||
assertThat(formParameters).contains("refresh_token=refresh-token"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); |
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()) |
||||
.containsExactly(this.accessToken.getScopes().toArray(new String[0])); |
||||
assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo(this.refreshToken.getTokenValue()); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasOriginalScope() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) |
||||
.build(); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration, |
||||
this.accessToken, this.refreshToken); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(refreshTokenGrantRequest); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()) |
||||
.containsExactly(this.accessToken.getScopes().toArray(new String[0])); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretPostThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) |
||||
.build(); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration, |
||||
this.accessToken, this.refreshToken); |
||||
this.tokenResponseClient.getTokenResponse(refreshTokenGrantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters).contains("client_id=client-id"); |
||||
assertThat(formParameters).contains("client_secret=client-secret"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretJwtThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) |
||||
.clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY) |
||||
.build(); |
||||
// @formatter:on
|
||||
|
||||
// Configure Jwt client authentication converter
|
||||
SecretKeySpec secretKey = new SecretKeySpec( |
||||
clientRegistration.getClientSecret().getBytes(StandardCharsets.UTF_8), "HmacSHA256"); |
||||
JWK jwk = TestJwks.jwk(secretKey).build(); |
||||
Function<ClientRegistration, JWK> jwkResolver = (registration) -> jwk; |
||||
configureJwtClientAuthenticationConverter(jwkResolver); |
||||
|
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration, |
||||
this.accessToken, this.refreshToken); |
||||
this.tokenResponseClient.getTokenResponse(refreshTokenGrantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters) |
||||
.contains("client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer"); |
||||
assertThat(formParameters).contains("client_assertion="); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationPrivateKeyJwtThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT) |
||||
.build(); |
||||
// @formatter:on
|
||||
|
||||
// Configure Jwt client authentication converter
|
||||
JWK jwk = TestJwks.DEFAULT_RSA_JWK; |
||||
Function<ClientRegistration, JWK> jwkResolver = (registration) -> jwk; |
||||
configureJwtClientAuthenticationConverter(jwkResolver); |
||||
|
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration, |
||||
this.accessToken, this.refreshToken); |
||||
this.tokenResponseClient.getTokenResponse(refreshTokenGrantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters) |
||||
.contains("client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer"); |
||||
assertThat(formParameters).contains("client_assertion="); |
||||
} |
||||
|
||||
private void configureJwtClientAuthenticationConverter(Function<ClientRegistration, JWK> jwkResolver) { |
||||
NimbusJwtClientAuthenticationParametersConverter<OAuth2RefreshTokenGrantRequest> jwtClientAuthenticationConverter = new NimbusJwtClientAuthenticationParametersConverter<>( |
||||
jwkResolver); |
||||
OAuth2RefreshTokenGrantRequestEntityConverter requestEntityConverter = new OAuth2RefreshTokenGrantRequestEntityConverter(); |
||||
requestEntityConverter.addParametersConverter(jwtClientAuthenticationConverter); |
||||
this.tokenResponseClient.setRequestEntityConverter(requestEntityConverter); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"not-bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest( |
||||
this.clientRegistration.build(), this.accessToken, this.refreshToken); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(refreshTokenGrantRequest)) |
||||
.withMessageContaining("[invalid_token_response] An error occurred while attempting to " |
||||
+ "retrieve the OAuth 2.0 Access Token Response") |
||||
.havingRootCause() |
||||
.withMessageContaining("tokenType cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasResponseScope() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest( |
||||
this.clientRegistration.build(), this.accessToken, this.refreshToken, Collections.singleton("read")); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient |
||||
.getTokenResponse(refreshTokenGrantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters).contains("scope=read"); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() { |
||||
String accessTokenErrorResponse = "{\n" + " \"error\": \"unauthorized_client\"\n" + "}\n"; |
||||
this.server.enqueue(jsonResponse(accessTokenErrorResponse).setResponseCode(400)); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest( |
||||
this.clientRegistration.build(), this.accessToken, this.refreshToken); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(refreshTokenGrantRequest)) |
||||
.withMessageContaining("[unauthorized_client]"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationException() { |
||||
this.server.enqueue(new MockResponse().setResponseCode(500)); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest( |
||||
this.clientRegistration.build(), this.accessToken, this.refreshToken); |
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(refreshTokenGrantRequest)) |
||||
.withMessageContaining("[invalid_token_response] An error occurred while attempting to " |
||||
+ "retrieve the OAuth 2.0 Access Token Response"); |
||||
} |
||||
|
||||
// gh-13144
|
||||
@Test |
||||
public void getTokenResponseWhenCustomClientAuthenticationMethodThenIllegalArgument() { |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(new ClientAuthenticationMethod("basic")) |
||||
.build(); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration, |
||||
this.accessToken, this.refreshToken); |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(refreshTokenGrantRequest)); |
||||
} |
||||
|
||||
// gh-13144
|
||||
@Test |
||||
public void getTokenResponseWhenUnsupportedClientAuthenticationMethodThenIllegalArgument() { |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) |
||||
.build(); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration, |
||||
this.accessToken, this.refreshToken); |
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(refreshTokenGrantRequest)); |
||||
} |
||||
|
||||
private MockResponse jsonResponse(String json) { |
||||
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json); |
||||
} |
||||
|
||||
} |
||||
@ -1,487 +0,0 @@
@@ -1,487 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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.io.IOException; |
||||
import java.net.URLEncoder; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.time.Instant; |
||||
|
||||
import okhttp3.mockwebserver.MockResponse; |
||||
import okhttp3.mockwebserver.MockWebServer; |
||||
import okhttp3.mockwebserver.RecordedRequest; |
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.RequestEntity; |
||||
import org.springframework.http.ResponseEntity; |
||||
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.OAuth2AccessToken; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException; |
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
||||
import org.springframework.security.oauth2.core.OAuth2Token; |
||||
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.security.oauth2.jwt.TestJwts; |
||||
import org.springframework.util.StringUtils; |
||||
import org.springframework.web.client.RestOperations; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.ArgumentMatchers.eq; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.spy; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
/** |
||||
* Tests for {@link DefaultJwtBearerTokenResponseClient}. |
||||
* |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
public class DefaultTokenExchangeTokenResponseClientTests { |
||||
|
||||
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; |
||||
|
||||
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; |
||||
|
||||
private DefaultTokenExchangeTokenResponseClient tokenResponseClient; |
||||
|
||||
private ClientRegistration.Builder clientRegistration; |
||||
|
||||
private OAuth2Token subjectToken; |
||||
|
||||
private OAuth2Token actorToken; |
||||
|
||||
private MockWebServer server; |
||||
|
||||
@BeforeEach |
||||
public void setUp() throws IOException { |
||||
this.tokenResponseClient = new DefaultTokenExchangeTokenResponseClient(); |
||||
this.server = new MockWebServer(); |
||||
this.server.start(); |
||||
String tokenUri = this.server.url("/oauth2/token").toString(); |
||||
// @formatter:off
|
||||
this.clientRegistration = TestClientRegistrations.clientCredentials() |
||||
.clientId("client-1") |
||||
.clientSecret("secret") |
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) |
||||
.tokenUri(tokenUri) |
||||
.scope("read", "write"); |
||||
// @formatter:on
|
||||
this.subjectToken = TestOAuth2AccessTokens.scopes("read", "write"); |
||||
this.actorToken = null; |
||||
} |
||||
|
||||
@AfterEach |
||||
public void cleanUp() throws IOException { |
||||
this.server.shutdown(); |
||||
} |
||||
|
||||
@Test |
||||
public void setRequestEntityConverterWhenConverterIsNullThenThrowIllegalArgumentException() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.tokenResponseClient.setRequestEntityConverter(null)) |
||||
.withMessage("requestEntityConverter cannot be null"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void setRestOperationsWhenRestOperationsIsNullThenThrowIllegalArgumentException() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.tokenResponseClient.setRestOperations(null)) |
||||
.withMessage("restOperations cannot be null"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenGrantRequestIsNullThenThrowIllegalArgumentException() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(null)) |
||||
.withMessage("grantRequest cannot be null"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read write\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); |
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) |
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
// @formatter:off
|
||||
assertThat(formParameters).contains( |
||||
param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), |
||||
param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), |
||||
param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), |
||||
param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), |
||||
param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) |
||||
); |
||||
// @formatter:on
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); |
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); |
||||
assertThat(accessTokenResponse.getRefreshToken()).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSubjectTokenIsJwtThenSubjectTokenTypeIsJwt() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read write\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600); |
||||
this.subjectToken = TestJwts.jwt().build(); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); |
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) |
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
// @formatter:off
|
||||
assertThat(formParameters).contains( |
||||
param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), |
||||
param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), |
||||
param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), |
||||
param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE), |
||||
param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) |
||||
); |
||||
// @formatter:on
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); |
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); |
||||
assertThat(accessTokenResponse.getRefreshToken()).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenActorTokenIsNotNullThenActorParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read write\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600); |
||||
this.actorToken = TestOAuth2AccessTokens.noScopes(); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); |
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) |
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
// @formatter:off
|
||||
assertThat(formParameters).contains( |
||||
param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), |
||||
param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), |
||||
param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), |
||||
param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), |
||||
param(OAuth2ParameterNames.ACTOR_TOKEN, this.actorToken.getTokenValue()), |
||||
param(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), |
||||
param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) |
||||
); |
||||
// @formatter:on
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); |
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); |
||||
assertThat(accessTokenResponse.getRefreshToken()).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenActorTokenIsJwtThenActorTokenTypeIsJwt() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read write\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600); |
||||
this.actorToken = TestJwts.jwt().build(); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); |
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString()); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE)) |
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
// @formatter:off
|
||||
assertThat(formParameters).contains( |
||||
param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), |
||||
param(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), |
||||
param(OAuth2ParameterNames.SUBJECT_TOKEN, this.subjectToken.getTokenValue()), |
||||
param(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE), |
||||
param(OAuth2ParameterNames.ACTOR_TOKEN, this.actorToken.getTokenValue()), |
||||
param(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE), |
||||
param(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(this.clientRegistration.build().getScopes(), " ")) |
||||
); |
||||
// @formatter:on
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); |
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); |
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactlyInAnyOrder("read", "write"); |
||||
assertThat(accessTokenResponse.getRefreshToken()).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretBasicThenAuthorizationHeaderIsSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
this.tokenResponseClient.getTokenResponse(grantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenAuthenticationClientSecretPostThenFormParametersAreSent() throws Exception { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) |
||||
.build(); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, |
||||
this.actorToken); |
||||
this.tokenResponseClient.getTokenResponse(grantRequest); |
||||
RecordedRequest recordedRequest = this.server.takeRequest(); |
||||
String formParameters = recordedRequest.getBody().readUtf8(); |
||||
assertThat(formParameters).contains("client_id=client-1", "client_secret=secret"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"not-bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest)) |
||||
.satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo("invalid_token_response")) |
||||
.withMessageContaining("[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response") |
||||
.havingRootCause().withMessage("tokenType cannot be null"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasResponseScope() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\",\n" |
||||
+ " \"scope\": \"read\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read"); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(grantRequest); |
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() { |
||||
String accessTokenErrorResponse = "{\"error\": \"invalid_grant\"}"; |
||||
this.server.enqueue(jsonResponse(accessTokenErrorResponse).setResponseCode(400)); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest)) |
||||
.satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_GRANT)) |
||||
.withMessageContaining("[invalid_grant]"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationException() { |
||||
this.server.enqueue(new MockResponse().setResponseCode(500)); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest)) |
||||
.satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo("invalid_token_response")) |
||||
.withMessageContaining("[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenCustomClientAuthenticationMethodThenIllegalArgument() { |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(new ClientAuthenticationMethod("basic")) |
||||
.build(); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, |
||||
this.actorToken); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest)) |
||||
.withMessageContaining("This class supports `client_secret_basic`, `client_secret_post`, and `none` by default."); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenUnsupportedClientAuthenticationMethodThenIllegalArgument() { |
||||
ClientRegistration clientRegistration = this.clientRegistration |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) |
||||
.build(); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, |
||||
this.actorToken); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(grantRequest)) |
||||
.withMessageContaining("This class supports `client_secret_basic`, `client_secret_post`, and `none` by default."); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenCustomRequestEntityConverterSetThenCalled() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
Converter<TokenExchangeGrantRequest, RequestEntity<?>> requestEntityConverter = spy( |
||||
TokenExchangeGrantRequestEntityConverter.class); |
||||
this.tokenResponseClient.setRequestEntityConverter(requestEntityConverter); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
this.tokenResponseClient.getTokenResponse(grantRequest); |
||||
verify(requestEntityConverter).convert(grantRequest); |
||||
} |
||||
|
||||
@Test |
||||
public void getTokenResponseWhenCustomRestOperationsSetThenCalled() { |
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n" |
||||
+ " \"access_token\": \"access-token-1234\",\n" |
||||
+ " \"token_type\": \"bearer\",\n" |
||||
+ " \"expires_in\": \"3600\"\n" |
||||
+ "}\n"; |
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); |
||||
RestOperations restOperations = mock(RestOperations.class); |
||||
given(restOperations.exchange(any(RequestEntity.class), eq(OAuth2AccessTokenResponse.class))) |
||||
.willReturn(new ResponseEntity<>(HttpStatus.OK)); |
||||
this.tokenResponseClient.setRestOperations(restOperations); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(this.clientRegistration.build(), |
||||
this.subjectToken, this.actorToken); |
||||
this.tokenResponseClient.getTokenResponse(grantRequest); |
||||
verify(restOperations).exchange(any(RequestEntity.class), eq(OAuth2AccessTokenResponse.class)); |
||||
} |
||||
|
||||
private MockResponse jsonResponse(String json) { |
||||
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json); |
||||
} |
||||
|
||||
private static String param(String parameterName, String parameterValue) { |
||||
return "%s=%s".formatted(parameterName, URLEncoder.encode(parameterValue, StandardCharsets.UTF_8)); |
||||
} |
||||
|
||||
} |
||||
@ -1,148 +0,0 @@
@@ -1,148 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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 org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.InOrder; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
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.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.security.oauth2.jwt.Jwt; |
||||
import org.springframework.security.oauth2.jwt.TestJwts; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.inOrder; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link JwtBearerGrantRequestEntityConverter}. |
||||
* |
||||
* @author Hassene Laaribi |
||||
* @author Joe Grandja |
||||
*/ |
||||
public class JwtBearerGrantRequestEntityConverterTests { |
||||
|
||||
private JwtBearerGrantRequestEntityConverter converter; |
||||
|
||||
@BeforeEach |
||||
public void setup() { |
||||
this.converter = new JwtBearerGrantRequestEntityConverter(); |
||||
} |
||||
|
||||
@Test |
||||
public void setHeadersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.setHeadersConverter(null)) |
||||
.withMessage("headersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void addHeadersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.addHeadersConverter(null)) |
||||
.withMessage("headersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void setParametersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.setParametersConverter(null)) |
||||
.withMessage("parametersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void addParametersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.addParametersConverter(null)) |
||||
.withMessage("parametersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenHeadersConverterSetThenCalled() { |
||||
Converter<JwtBearerGrantRequest, HttpHeaders> headersConverter1 = mock(Converter.class); |
||||
this.converter.setHeadersConverter(headersConverter1); |
||||
Converter<JwtBearerGrantRequest, HttpHeaders> headersConverter2 = mock(Converter.class); |
||||
this.converter.addHeadersConverter(headersConverter2); |
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER) |
||||
.scope("read", "write") |
||||
.build(); |
||||
// @formatter:on
|
||||
Jwt jwtAssertion = TestJwts.jwt().build(); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(clientRegistration, jwtAssertion); |
||||
this.converter.convert(jwtBearerGrantRequest); |
||||
InOrder inOrder = inOrder(headersConverter1, headersConverter2); |
||||
inOrder.verify(headersConverter1).convert(any(JwtBearerGrantRequest.class)); |
||||
inOrder.verify(headersConverter2).convert(any(JwtBearerGrantRequest.class)); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenParametersConverterSetThenCalled() { |
||||
Converter<JwtBearerGrantRequest, MultiValueMap<String, String>> parametersConverter1 = mock(Converter.class); |
||||
this.converter.setParametersConverter(parametersConverter1); |
||||
Converter<JwtBearerGrantRequest, MultiValueMap<String, String>> parametersConverter2 = mock(Converter.class); |
||||
this.converter.addParametersConverter(parametersConverter2); |
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER) |
||||
.scope("read", "write") |
||||
.build(); |
||||
// @formatter:on
|
||||
Jwt jwtAssertion = TestJwts.jwt().build(); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(clientRegistration, jwtAssertion); |
||||
this.converter.convert(jwtBearerGrantRequest); |
||||
InOrder inOrder = inOrder(parametersConverter1, parametersConverter2); |
||||
inOrder.verify(parametersConverter1).convert(any(JwtBearerGrantRequest.class)); |
||||
inOrder.verify(parametersConverter2).convert(any(JwtBearerGrantRequest.class)); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void convertWhenGrantRequestValidThenConverts() { |
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER) |
||||
.scope("read", "write") |
||||
.build(); |
||||
// @formatter:on
|
||||
Jwt jwtAssertion = TestJwts.jwt().build(); |
||||
JwtBearerGrantRequest jwtBearerGrantRequest = new JwtBearerGrantRequest(clientRegistration, jwtAssertion); |
||||
RequestEntity<?> requestEntity = this.converter.convert(jwtBearerGrantRequest); |
||||
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); |
||||
assertThat(headers.getContentType()) |
||||
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); |
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); |
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody(); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) |
||||
.isEqualTo(AuthorizationGrantType.JWT_BEARER.getValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.ASSERTION)).isEqualTo(jwtAssertion.getTokenValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("read write"); |
||||
} |
||||
|
||||
} |
||||
@ -1,196 +0,0 @@
@@ -1,196 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.InOrder; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
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.endpoint.OAuth2AuthorizationExchange; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; |
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges; |
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; |
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.inOrder; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link OAuth2AuthorizationCodeGrantRequestEntityConverter}. |
||||
* |
||||
* @author Joe Grandja |
||||
*/ |
||||
public class OAuth2AuthorizationCodeGrantRequestEntityConverterTests { |
||||
|
||||
private OAuth2AuthorizationCodeGrantRequestEntityConverter converter; |
||||
|
||||
@BeforeEach |
||||
public void setup() { |
||||
this.converter = new OAuth2AuthorizationCodeGrantRequestEntityConverter(); |
||||
} |
||||
|
||||
@Test |
||||
public void setHeadersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.setHeadersConverter(null)) |
||||
.withMessage("headersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void addHeadersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.addHeadersConverter(null)) |
||||
.withMessage("headersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void setParametersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.setParametersConverter(null)) |
||||
.withMessage("parametersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void addParametersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.addParametersConverter(null)) |
||||
.withMessage("parametersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenHeadersConverterSetThenCalled() { |
||||
Converter<OAuth2AuthorizationCodeGrantRequest, HttpHeaders> headersConverter1 = mock(Converter.class); |
||||
this.converter.setHeadersConverter(headersConverter1); |
||||
Converter<OAuth2AuthorizationCodeGrantRequest, HttpHeaders> headersConverter2 = mock(Converter.class); |
||||
this.converter.addHeadersConverter(headersConverter2); |
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); |
||||
OAuth2AuthorizationExchange authorizationExchange = TestOAuth2AuthorizationExchanges.success(); |
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest = new OAuth2AuthorizationCodeGrantRequest( |
||||
clientRegistration, authorizationExchange); |
||||
this.converter.convert(authorizationCodeGrantRequest); |
||||
InOrder inOrder = inOrder(headersConverter1, headersConverter2); |
||||
inOrder.verify(headersConverter1).convert(any(OAuth2AuthorizationCodeGrantRequest.class)); |
||||
inOrder.verify(headersConverter2).convert(any(OAuth2AuthorizationCodeGrantRequest.class)); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenParametersConverterSetThenCalled() { |
||||
Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter1 = mock( |
||||
Converter.class); |
||||
this.converter.setParametersConverter(parametersConverter1); |
||||
Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter2 = mock( |
||||
Converter.class); |
||||
this.converter.addParametersConverter(parametersConverter2); |
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); |
||||
OAuth2AuthorizationExchange authorizationExchange = TestOAuth2AuthorizationExchanges.success(); |
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest = new OAuth2AuthorizationCodeGrantRequest( |
||||
clientRegistration, authorizationExchange); |
||||
this.converter.convert(authorizationCodeGrantRequest); |
||||
InOrder inOrder = inOrder(parametersConverter1, parametersConverter2); |
||||
inOrder.verify(parametersConverter1).convert(any(OAuth2AuthorizationCodeGrantRequest.class)); |
||||
inOrder.verify(parametersConverter2).convert(any(OAuth2AuthorizationCodeGrantRequest.class)); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void convertWhenGrantRequestValidThenConverts() { |
||||
// @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(); |
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest = new OAuth2AuthorizationCodeGrantRequest( |
||||
clientRegistration, authorizationExchange); |
||||
RequestEntity<?> requestEntity = this.converter.convert(authorizationCodeGrantRequest); |
||||
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); |
||||
assertThat(headers.getContentType()) |
||||
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); |
||||
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()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.CODE)).isEqualTo(authorizationResponse.getCode()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.CLIENT_ID)).isNull(); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI)) |
||||
.isEqualTo(authorizationRequest.getRedirectUri()); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void convertWhenPkceGrantRequestValidThenConverts() { |
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.clientAuthenticationMethod(null) |
||||
.clientSecret(null) |
||||
.build(); |
||||
Map<String, Object> attributes = new HashMap<>(); |
||||
attributes.put(PkceParameterNames.CODE_VERIFIER, "code-verifier-1234"); |
||||
Map<String, Object> additionalParameters = new HashMap<>(); |
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge-1234"); |
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); |
||||
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() |
||||
.attributes(attributes) |
||||
.additionalParameters(additionalParameters) |
||||
.build(); |
||||
OAuth2AuthorizationResponse authorizationResponse = TestOAuth2AuthorizationResponses.success().build(); |
||||
OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest, |
||||
authorizationResponse); |
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest = new OAuth2AuthorizationCodeGrantRequest( |
||||
clientRegistration, authorizationExchange); |
||||
RequestEntity<?> requestEntity = this.converter.convert(authorizationCodeGrantRequest); |
||||
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); |
||||
assertThat(headers.getContentType()) |
||||
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); |
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody(); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) |
||||
.isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.CODE)).isEqualTo(authorizationResponse.getCode()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI)) |
||||
.isEqualTo(authorizationRequest.getRedirectUri()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.CLIENT_ID)) |
||||
.isEqualTo(authorizationRequest.getClientId()); |
||||
assertThat(formParameters.getFirst(PkceParameterNames.CODE_VERIFIER)) |
||||
.isEqualTo(authorizationRequest.getAttribute(PkceParameterNames.CODE_VERIFIER)); |
||||
} |
||||
|
||||
} |
||||
@ -1,170 +0,0 @@
@@ -1,170 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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.io.UnsupportedEncodingException; |
||||
import java.net.URLEncoder; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.Base64; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.InOrder; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
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.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.inOrder; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link OAuth2ClientCredentialsGrantRequestEntityConverter}. |
||||
* |
||||
* @author Joe Grandja |
||||
*/ |
||||
public class OAuth2ClientCredentialsGrantRequestEntityConverterTests { |
||||
|
||||
private OAuth2ClientCredentialsGrantRequestEntityConverter converter; |
||||
|
||||
@BeforeEach |
||||
public void setup() { |
||||
this.converter = new OAuth2ClientCredentialsGrantRequestEntityConverter(); |
||||
} |
||||
|
||||
@Test |
||||
public void setHeadersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.setHeadersConverter(null)) |
||||
.withMessage("headersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void addHeadersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.addHeadersConverter(null)) |
||||
.withMessage("headersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void setParametersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.setParametersConverter(null)) |
||||
.withMessage("parametersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void addParametersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.addParametersConverter(null)) |
||||
.withMessage("parametersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenHeadersConverterSetThenCalled() { |
||||
Converter<OAuth2ClientCredentialsGrantRequest, HttpHeaders> headersConverter1 = mock(Converter.class); |
||||
this.converter.setHeadersConverter(headersConverter1); |
||||
Converter<OAuth2ClientCredentialsGrantRequest, HttpHeaders> headersConverter2 = mock(Converter.class); |
||||
this.converter.addHeadersConverter(headersConverter2); |
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials().build(); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
clientRegistration); |
||||
this.converter.convert(clientCredentialsGrantRequest); |
||||
InOrder inOrder = inOrder(headersConverter1, headersConverter2); |
||||
inOrder.verify(headersConverter1).convert(any(OAuth2ClientCredentialsGrantRequest.class)); |
||||
inOrder.verify(headersConverter2).convert(any(OAuth2ClientCredentialsGrantRequest.class)); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenParametersConverterSetThenCalled() { |
||||
Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> parametersConverter1 = mock( |
||||
Converter.class); |
||||
this.converter.setParametersConverter(parametersConverter1); |
||||
Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> parametersConverter2 = mock( |
||||
Converter.class); |
||||
this.converter.addParametersConverter(parametersConverter2); |
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials().build(); |
||||
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( |
||||
clientRegistration); |
||||
this.converter.convert(clientCredentialsGrantRequest); |
||||
InOrder inOrder = inOrder(parametersConverter1, parametersConverter2); |
||||
inOrder.verify(parametersConverter1).convert(any(OAuth2ClientCredentialsGrantRequest.class)); |
||||
inOrder.verify(parametersConverter2).convert(any(OAuth2ClientCredentialsGrantRequest.class)); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void convertWhenGrantRequestValidThenConverts() { |
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials().build(); |
||||
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); |
||||
assertThat(headers.getContentType()) |
||||
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); |
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); |
||||
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()); |
||||
} |
||||
|
||||
// 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); |
||||
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,144 +0,0 @@
@@ -1,144 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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.util.Collections; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.InOrder; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
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.OAuth2AccessToken; |
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken; |
||||
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; |
||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.inOrder; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link OAuth2RefreshTokenGrantRequestEntityConverter}. |
||||
* |
||||
* @author Joe Grandja |
||||
*/ |
||||
public class OAuth2RefreshTokenGrantRequestEntityConverterTests { |
||||
|
||||
private OAuth2RefreshTokenGrantRequestEntityConverter converter; |
||||
|
||||
@BeforeEach |
||||
public void setup() { |
||||
this.converter = new OAuth2RefreshTokenGrantRequestEntityConverter(); |
||||
} |
||||
|
||||
@Test |
||||
public void setHeadersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.setHeadersConverter(null)) |
||||
.withMessage("headersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void addHeadersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.addHeadersConverter(null)) |
||||
.withMessage("headersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void setParametersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.setParametersConverter(null)) |
||||
.withMessage("parametersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void addParametersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.addParametersConverter(null)) |
||||
.withMessage("parametersConverter cannot be null"); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenHeadersConverterSetThenCalled() { |
||||
Converter<OAuth2RefreshTokenGrantRequest, HttpHeaders> headersConverter1 = mock(Converter.class); |
||||
this.converter.setHeadersConverter(headersConverter1); |
||||
Converter<OAuth2RefreshTokenGrantRequest, HttpHeaders> headersConverter2 = mock(Converter.class); |
||||
this.converter.addHeadersConverter(headersConverter2); |
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); |
||||
OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("read", "write"); |
||||
OAuth2RefreshToken refreshToken = TestOAuth2RefreshTokens.refreshToken(); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration, |
||||
accessToken, refreshToken); |
||||
this.converter.convert(refreshTokenGrantRequest); |
||||
InOrder inOrder = inOrder(headersConverter1, headersConverter2); |
||||
inOrder.verify(headersConverter1).convert(any(OAuth2RefreshTokenGrantRequest.class)); |
||||
inOrder.verify(headersConverter2).convert(any(OAuth2RefreshTokenGrantRequest.class)); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenParametersConverterSetThenCalled() { |
||||
Converter<OAuth2RefreshTokenGrantRequest, MultiValueMap<String, String>> parametersConverter1 = mock( |
||||
Converter.class); |
||||
this.converter.setParametersConverter(parametersConverter1); |
||||
Converter<OAuth2RefreshTokenGrantRequest, MultiValueMap<String, String>> parametersConverter2 = mock( |
||||
Converter.class); |
||||
this.converter.addParametersConverter(parametersConverter2); |
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); |
||||
OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("read", "write"); |
||||
OAuth2RefreshToken refreshToken = TestOAuth2RefreshTokens.refreshToken(); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration, |
||||
accessToken, refreshToken); |
||||
this.converter.convert(refreshTokenGrantRequest); |
||||
InOrder inOrder = inOrder(parametersConverter1, parametersConverter2); |
||||
inOrder.verify(parametersConverter1).convert(any(OAuth2RefreshTokenGrantRequest.class)); |
||||
inOrder.verify(parametersConverter2).convert(any(OAuth2RefreshTokenGrantRequest.class)); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void convertWhenGrantRequestValidThenConverts() { |
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); |
||||
OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("read", "write"); |
||||
OAuth2RefreshToken refreshToken = TestOAuth2RefreshTokens.refreshToken(); |
||||
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration, |
||||
accessToken, refreshToken, Collections.singleton("read")); |
||||
RequestEntity<?> requestEntity = this.converter.convert(refreshTokenGrantRequest); |
||||
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); |
||||
assertThat(headers.getContentType()) |
||||
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); |
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); |
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody(); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) |
||||
.isEqualTo(AuthorizationGrantType.REFRESH_TOKEN.getValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN)).isEqualTo(refreshToken.getTokenValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("read"); |
||||
} |
||||
|
||||
} |
||||
@ -1,306 +0,0 @@
@@ -1,306 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2025 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 org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.InOrder; |
||||
|
||||
import org.springframework.core.convert.converter.Converter; |
||||
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.OAuth2Token; |
||||
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.security.oauth2.jwt.TestJwts; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.inOrder; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link TokenExchangeGrantRequestEntityConverter}. |
||||
* |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
public class TokenExchangeGrantRequestEntityConverterTests { |
||||
|
||||
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; |
||||
|
||||
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; |
||||
|
||||
private TokenExchangeGrantRequestEntityConverter converter; |
||||
|
||||
private OAuth2Token subjectToken; |
||||
|
||||
private OAuth2Token actorToken; |
||||
|
||||
@BeforeEach |
||||
public void setUp() { |
||||
this.converter = new TokenExchangeGrantRequestEntityConverter(); |
||||
this.subjectToken = TestOAuth2AccessTokens.scopes("read", "write"); |
||||
this.actorToken = null; |
||||
} |
||||
|
||||
@Test |
||||
public void setHeadersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.converter.setHeadersConverter(null)) |
||||
.withMessage("headersConverter cannot be null"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void addHeadersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.converter.addHeadersConverter(null)) |
||||
.withMessage("headersConverter cannot be null"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void setParametersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.converter.setParametersConverter(null)) |
||||
.withMessage("parametersConverter cannot be null"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void addParametersConverterWhenNullThenThrowIllegalArgumentException() { |
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy(() -> this.converter.addParametersConverter(null)) |
||||
.withMessage("parametersConverter cannot be null"); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenHeadersConverterSetThenCalled() { |
||||
Converter<TokenExchangeGrantRequest, HttpHeaders> headersConverter1 = mock(Converter.class); |
||||
this.converter.setHeadersConverter(headersConverter1); |
||||
Converter<TokenExchangeGrantRequest, HttpHeaders> headersConverter2 = mock(Converter.class); |
||||
this.converter.addHeadersConverter(headersConverter2); |
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) |
||||
.scope("read", "write") |
||||
.build(); |
||||
// @formatter:on
|
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, |
||||
this.actorToken); |
||||
this.converter.convert(grantRequest); |
||||
InOrder inOrder = inOrder(headersConverter1, headersConverter2); |
||||
inOrder.verify(headersConverter1).convert(grantRequest); |
||||
inOrder.verify(headersConverter2).convert(grantRequest); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenParametersConverterSetThenCalled() { |
||||
Converter<TokenExchangeGrantRequest, MultiValueMap<String, String>> parametersConverter1 = mock( |
||||
Converter.class); |
||||
this.converter.setParametersConverter(parametersConverter1); |
||||
Converter<TokenExchangeGrantRequest, MultiValueMap<String, String>> parametersConverter2 = mock( |
||||
Converter.class); |
||||
this.converter.addParametersConverter(parametersConverter2); |
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) |
||||
.scope("read", "write") |
||||
.build(); |
||||
// @formatter:on
|
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, |
||||
this.actorToken); |
||||
this.converter.convert(grantRequest); |
||||
InOrder inOrder = inOrder(parametersConverter1, parametersConverter2); |
||||
inOrder.verify(parametersConverter1).convert(any(TokenExchangeGrantRequest.class)); |
||||
inOrder.verify(parametersConverter2).convert(any(TokenExchangeGrantRequest.class)); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void convertWhenGrantRequestValidThenConverts() { |
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) |
||||
.scope("read", "write") |
||||
.build(); |
||||
// @formatter:on
|
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, |
||||
this.actorToken); |
||||
RequestEntity<?> requestEntity = this.converter.convert(grantRequest); |
||||
assertThat(requestEntity).isNotNull(); |
||||
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); |
||||
assertThat(headers.getContentType()) |
||||
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); |
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); |
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody(); |
||||
assertThat(formParameters).isNotNull(); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) |
||||
.isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)) |
||||
.isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN)) |
||||
.isEqualTo(this.subjectToken.getTokenValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)) |
||||
.isEqualTo(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void convertWhenClientAuthenticationMethodIsClientSecretPostThenClientIdAndSecretParametersPresent() { |
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) |
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) |
||||
.scope("read", "write") |
||||
.build(); |
||||
// @formatter:on
|
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, |
||||
this.actorToken); |
||||
RequestEntity<?> requestEntity = this.converter.convert(grantRequest); |
||||
assertThat(requestEntity).isNotNull(); |
||||
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); |
||||
assertThat(headers.getContentType()) |
||||
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); |
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isNull(); |
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody(); |
||||
assertThat(formParameters).isNotNull(); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) |
||||
.isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)) |
||||
.isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN)) |
||||
.isEqualTo(this.subjectToken.getTokenValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)) |
||||
.isEqualTo(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.CLIENT_ID)).isEqualTo(clientRegistration.getClientId()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.CLIENT_SECRET)) |
||||
.isEqualTo(clientRegistration.getClientSecret()); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void convertWhenActorTokenIsNotNullThenActorTokenParametersPresent() { |
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) |
||||
.scope("read", "write") |
||||
.build(); |
||||
// @formatter:on
|
||||
this.actorToken = TestOAuth2AccessTokens.noScopes(); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, |
||||
this.actorToken); |
||||
RequestEntity<?> requestEntity = this.converter.convert(grantRequest); |
||||
assertThat(requestEntity).isNotNull(); |
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody(); |
||||
assertThat(formParameters).isNotNull(); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) |
||||
.isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)) |
||||
.isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN)) |
||||
.isEqualTo(this.subjectToken.getTokenValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN)) |
||||
.isEqualTo(this.actorToken.getTokenValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN_TYPE)).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)) |
||||
.isEqualTo(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void convertWhenSubjectTokenIsJwtThenSubjectTokenTypeIsJwt() { |
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) |
||||
.scope("read", "write") |
||||
.build(); |
||||
// @formatter:on
|
||||
this.subjectToken = TestJwts.jwt().build(); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, |
||||
this.actorToken); |
||||
RequestEntity<?> requestEntity = this.converter.convert(grantRequest); |
||||
assertThat(requestEntity).isNotNull(); |
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody(); |
||||
assertThat(formParameters).isNotNull(); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) |
||||
.isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)) |
||||
.isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN)) |
||||
.isEqualTo(this.subjectToken.getTokenValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)) |
||||
.isEqualTo(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Test |
||||
public void convertWhenActorTokenIsJwtThenActorTokenTypeIsJwt() { |
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration() |
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) |
||||
.scope("read", "write") |
||||
.build(); |
||||
// @formatter:on
|
||||
this.actorToken = TestJwts.jwt().build(); |
||||
TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, this.subjectToken, |
||||
this.actorToken); |
||||
RequestEntity<?> requestEntity = this.converter.convert(grantRequest); |
||||
assertThat(requestEntity).isNotNull(); |
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody(); |
||||
assertThat(formParameters).isNotNull(); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) |
||||
.isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)) |
||||
.isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN)) |
||||
.isEqualTo(this.subjectToken.getTokenValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN)) |
||||
.isEqualTo(this.actorToken.getTokenValue()); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN_TYPE)).isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)) |
||||
.isEqualTo(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " ")); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue