From e869bcdfa3db194d2f2e03a76f7eef77e541969e Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Wed, 2 Jul 2025 11:50:24 -0400 Subject: [PATCH] Remove deprecated implementations of OAuth2AccessTokenResponseClient Closes gh-16909 --- .../oauth2/client/OAuth2ClientConfigurer.java | 4 +- .../oauth2/client/OAuth2LoginConfigurer.java | 4 +- .../OAuth2ClientBeanDefinitionParser.java | 7 +- .../http/OAuth2LoginBeanDefinitionParser.java | 7 +- .../oauth2/client/authorization-grants.adoc | 70 +-- .../ROOT/pages/servlet/oauth2/index.adoc | 183 +++---- ...t-client-access-token-response-client.adoc | 32 -- etc/checkstyle/checkstyle-suppressions.xml | 1 - ...entialsOAuth2AuthorizedClientProvider.java | 8 +- ...tBearerOAuth2AuthorizedClientProvider.java | 8 +- ...shTokenOAuth2AuthorizedClientProvider.java | 6 +- ...xchangeOAuth2AuthorizedClientProvider.java | 8 +- ...horizationGrantRequestEntityConverter.java | 170 ------ ...ethodValidatingRequestEntityConverter.java | 48 -- ...tAuthorizationCodeTokenResponseClient.java | 138 ----- ...tClientCredentialsTokenResponseClient.java | 135 ----- .../DefaultJwtBearerTokenResponseClient.java | 130 ----- ...ultOAuth2TokenRequestHeadersConverter.java | 19 +- ...efaultRefreshTokenTokenResponseClient.java | 144 ------ ...faultTokenExchangeTokenResponseClient.java | 128 ----- .../JwtBearerGrantRequestEntityConverter.java | 63 --- ...ientAuthenticationParametersConverter.java | 6 +- ...zationCodeGrantRequestEntityConverter.java | 72 --- ...redentialsGrantRequestEntityConverter.java | 62 --- ...freshTokenGrantRequestEntityConverter.java | 62 --- ...enExchangeGrantRequestEntityConverter.java | 81 --- ...uthorizedClientExchangeFilterFunction.java | 4 +- ...orizationCodeTokenResponseClientTests.java | 422 --------------- ...ntCredentialsTokenResponseClientTests.java | 413 --------------- ...aultJwtBearerTokenResponseClientTests.java | 274 ---------- ...uth2TokenRequestHeadersConverterTests.java | 35 -- ...tRefreshTokenTokenResponseClientTests.java | 347 ------------- ...TokenExchangeTokenResponseClientTests.java | 487 ------------------ ...earerGrantRequestEntityConverterTests.java | 148 ------ ...nCodeGrantRequestEntityConverterTests.java | 196 ------- ...tialsGrantRequestEntityConverterTests.java | 170 ------ ...TokenGrantRequestEntityConverterTests.java | 144 ------ ...hangeGrantRequestEntityConverterTests.java | 306 ----------- 38 files changed, 118 insertions(+), 4424 deletions(-) delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractOAuth2AuthorizationGrantRequestEntityConverter.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/ClientAuthenticationMethodValidatingRequestEntityConverter.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClient.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClient.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClient.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverter.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverter.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverter.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverter.java delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverter.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClientTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverterTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java index 9ed4da02fe..e258463817 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java @@ -24,9 +24,9 @@ import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationProvider; -import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.client.endpoint.RestClientAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; @@ -297,7 +297,7 @@ public final class OAuth2ClientConfigurer> ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class, OAuth2AuthorizationCodeGrantRequest.class); OAuth2AccessTokenResponseClient bean = getBeanOrNull(resolvableType); - return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient(); + return (bean != null) ? bean : new RestClientAuthorizationCodeTokenResponseClient(); } private ClientRegistrationRepository getClientRegistrationRepository(B builder) { diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java index 27dfc8f7aa..d1222167c1 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java @@ -53,9 +53,9 @@ import org.springframework.security.core.session.SessionIdChangedEvent; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider; import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken; -import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.client.endpoint.RestClientAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider; import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizedClientRefreshedEventListener; import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry; @@ -462,7 +462,7 @@ public final class OAuth2LoginConfigurer> ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class, OAuth2AuthorizationCodeGrantRequest.class); OAuth2AccessTokenResponseClient bean = getBeanOrNull(resolvableType); - return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient(); + return (bean != null) ? bean : new RestClientAuthorizationCodeTokenResponseClient(); } private OAuth2UserService getOidcUserService() { diff --git a/config/src/main/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParser.java index df4dc2d586..13da21f3e1 100644 --- a/config/src/main/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -150,9 +150,8 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser { if (StringUtils.hasLength(accessTokenResponseClientRef)) { return new RuntimeBeanReference(accessTokenResponseClientRef); } - return BeanDefinitionBuilder - .rootBeanDefinition( - "org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient") + return BeanDefinitionBuilder.rootBeanDefinition( + "org.springframework.security.oauth2.client.endpoint.RestClientAuthorizationCodeTokenResponseClient") .getBeanDefinition(); } diff --git a/config/src/main/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParser.java index 176cdcad0d..eb2b6ae5a2 100644 --- a/config/src/main/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -334,9 +334,8 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser { if (StringUtils.hasLength(accessTokenResponseClientRef)) { return new RuntimeBeanReference(accessTokenResponseClientRef); } - return BeanDefinitionBuilder - .rootBeanDefinition( - "org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient") + return BeanDefinitionBuilder.rootBeanDefinition( + "org.springframework.security.oauth2.client.endpoint.RestClientAuthorizationCodeTokenResponseClient") .getBeanDefinition(); } diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc index d8ea7f1bb4..7c7b1dc1e7 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc @@ -370,19 +370,7 @@ Xml:: See the https://tools.ietf.org/html/rfc6749#section-4.1.3[Access Token Request/Response] protocol flow for the Authorization Code grant. ==== -There are two implementations of `OAuth2AccessTokenResponseClient` that can be used to make HTTP requests to the Token Endpoint in order to obtain an access token for the Authorization Code grant: - -* `DefaultAuthorizationCodeTokenResponseClient` (_default_) -* `RestClientAuthorizationCodeTokenResponseClient` - -The default implementation uses a `RestOperations` instance to exchange an authorization code for an access token at the Authorization Server’s Token Endpoint. -Spring Security 6.4 introduces a new implementation based on `RestClient`, which provides similar functionality but is better aligned with the Reactive version of the component (based on `WebClient`) in order to provide consistent configuration for applications on either stack. - -[NOTE] -==== -This section focuses on `RestClientAuthorizationCodeTokenResponseClient`. -You can read about {spring-security-reference-base-url}/6.3/servlet/oauth2/client/authorization-grants.html#_requesting_an_access_token[`DefaultAuthorizationCodeTokenResponseClient`] in the Spring Security 6.3 documentation. -==== +The default implementation of `OAuth2AccessTokenResponseClient` for the Authorization Code grant is `RestClientAuthorizationCodeTokenResponseClient`, which uses a `RestClient` instance to exchange an authorization code for an access token at the Authorization Server’s Token Endpoint. :section-id: authorization-code :grant-type: Authorization Code @@ -473,19 +461,7 @@ See the OAuth 2.0 Authorization Framework for further details on the https://too See the https://tools.ietf.org/html/rfc6749#section-6[Access Token Request/Response] protocol flow for the Refresh Token grant. ==== -There are two implementations of `OAuth2AccessTokenResponseClient` that can be used to make HTTP requests to the Token Endpoint in order to obtain an access token for the Refresh Token grant: - -* `DefaultRefreshTokenTokenResponseClient` (_default_) -* `RestClientRefreshTokenTokenResponseClient` - -The default implementation uses a `RestOperations` instance to exchange an authorization code for an access token at the Authorization Server’s Token Endpoint. -Spring Security 6.4 introduces a new implementation based on `RestClient`, which provides similar functionality but is better aligned with the Reactive version of the component (based on `WebClient`) in order to provide consistent configuration for applications on either stack. - -[NOTE] -==== -This section focuses on `RestClientRefreshTokenTokenResponseClient`. -You can read about {spring-security-reference-base-url}/6.3/servlet/oauth2/client/authorization-grants.html#_refreshing_an_access_token[`DefaultRefreshTokenTokenResponseClient`] in the Spring Security 6.3 documentation. -==== +The default implementation of `OAuth2AccessTokenResponseClient` for the Refresh Token grant is `RestClientRefreshTokenTokenResponseClient`, which uses a `RestClient` instance to obtain an access token at the Authorization Server’s Token Endpoint. :section-id: refresh-token :grant-type: Refresh Token @@ -565,19 +541,7 @@ Please refer to the OAuth 2.0 Authorization Framework for further details on the See the https://tools.ietf.org/html/rfc6749#section-4.4.2[Access Token Request/Response] protocol flow for the Client Credentials grant. ==== -There are two implementations of `OAuth2AccessTokenResponseClient` that can be used to make HTTP requests to the Token Endpoint in order to obtain an access token for the Client Credentials grant: - -* `DefaultClientCredentialsTokenResponseClient` (_default_) -* `RestClientClientCredentialsTokenResponseClient` - -The default implementation uses a `RestOperations` instance to exchange an authorization code for an access token at the Authorization Server’s Token Endpoint. -Spring Security 6.4 introduces a new implementation based on `RestClient`, which provides similar functionality but is better aligned with the Reactive version of the component (based on `WebClient`) in order to provide consistent configuration for applications on either stack. - -[NOTE] -==== -This section focuses on `RestClientClientCredentialsTokenResponseClient`. -You can read about {spring-security-reference-base-url}/6.3/servlet/oauth2/client/authorization-grants.html#_requesting_an_access_token_2[`DefaultClientCredentialsTokenResponseClient`] in the Spring Security 6.3 documentation. -==== +The default implementation of `OAuth2AccessTokenResponseClient` for the Client Credentials grant is `RestClientClientCredentialsTokenResponseClient`, which uses a `RestClient` instance to obtain an access token at the Authorization Server’s Token Endpoint. :section-id: client-credentials :grant-type: Client Credentials @@ -794,19 +758,7 @@ Please refer to JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication Please refer to the https://datatracker.ietf.org/doc/html/rfc7523#section-2.1[Access Token Request/Response] protocol flow for the JWT Bearer grant. ==== -There are two implementations of `OAuth2AccessTokenResponseClient` that can be used to make HTTP requests to the Token Endpoint in order to obtain an access token for the JWT Bearer grant: - -* `DefaultJwtBearerTokenResponseClient` (_default_) -* `RestClientJwtBearerTokenResponseClient` - -The default implementation uses a `RestOperations` instance to exchange an authorization code for an access token at the Authorization Server’s Token Endpoint. -Spring Security 6.4 introduces a new implementation based on `RestClient`, which provides similar functionality but is better aligned with the Reactive version of the component (based on `WebClient`) in order to provide consistent configuration for applications on either stack. - -[NOTE] -==== -This section focuses on `RestClientJwtBearerTokenResponseClient`. -You can read about {spring-security-reference-base-url}/6.3/servlet/oauth2/client/authorization-grants.html#_requesting_an_access_token_4[`DefaultClientCredentialsTokenResponseClient`] in the Spring Security 6.3 documentation. -==== +The default implementation of `OAuth2AccessTokenResponseClient` for the JWT Bearer grant is `RestClientJwtBearerTokenResponseClient`, which uses a `RestClient` instance to obtain an access token at the Authorization Server’s Token Endpoint. :section-id: jwt-bearer :grant-type: JWT Bearer @@ -1015,19 +967,7 @@ Please refer to OAuth 2.0 Token Exchange for further details on the https://data Please refer to the https://datatracker.ietf.org/doc/html/rfc8693#section-2[Token Exchange Request and Response] protocol flow for the Token Exchange grant. ==== -There are two implementations of `OAuth2AccessTokenResponseClient` that can be used to make HTTP requests to the Token Endpoint in order to obtain an access token for the Token Exchange grant: - -* `DefaultTokenExchangeTokenResponseClient` (_default_) -* `RestClientTokenExchangeTokenResponseClient` - -The default implementation uses a `RestOperations` instance to exchange an authorization code for an access token at the Authorization Server’s Token Endpoint. -Spring Security 6.4 introduces a new implementation based on `RestClient`, which provides similar functionality but is better aligned with the Reactive version of the component (based on `WebClient`) in order to provide consistent configuration for applications on either stack. - -[NOTE] -==== -This section focuses on `RestClientTokenExchangeTokenResponseClient`. -You can read about {spring-security-reference-base-url}/6.3/servlet/oauth2/client/authorization-grants.html#_requesting_an_access_token_5[`DefaultTokenExchangeTokenResponseClient`] in the Spring Security 6.3 documentation. -==== +The default implementation of `OAuth2AccessTokenResponseClient` for the Token Exchange grant is `RestClientTokenExchangeTokenResponseClient`, which uses a `RestClient` instance to obtain an access token at the Authorization Server’s Token Endpoint. :section-id: token-exchange :grant-type: Token Exchange diff --git a/docs/modules/ROOT/pages/servlet/oauth2/index.adoc b/docs/modules/ROOT/pages/servlet/oauth2/index.adoc index 732267449f..599fe78d24 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/index.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/index.adoc @@ -406,7 +406,7 @@ Consider the following use cases for OAuth2 Client: * I want to <> * I want to <> * I want to <> -* I want to <> +* I want to <> [[oauth2-client-log-users-in]] === Log Users In with OAuth2 @@ -1480,13 +1480,9 @@ public class SecurityConfig { @Bean public OAuth2AccessTokenResponseClient authorizationCodeAccessTokenResponseClient() { - OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter = - new OAuth2AuthorizationCodeGrantRequestEntityConverter(); - requestEntityConverter.addParametersConverter(parametersConverter()); - - DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient = - new DefaultAuthorizationCodeTokenResponseClient(); - accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter); + RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient = + new RestClientAuthorizationCodeTokenResponseClient(); + accessTokenResponseClient.addParametersConverter(parametersConverter()); return accessTokenResponseClient; } @@ -1512,11 +1508,8 @@ class SecurityConfig { @Bean fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient { - val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter() - requestEntityConverter.addParametersConverter(parametersConverter()) - - val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient() - accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter) + val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient() + accessTokenResponseClient.addParametersConverter(parametersConverter()) return accessTokenResponseClient } @@ -1555,13 +1548,9 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter = - new OAuth2AuthorizationCodeGrantRequestEntityConverter(); - requestEntityConverter.addParametersConverter(parametersConverter()); - - DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient = - new DefaultAuthorizationCodeTokenResponseClient(); - accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter); + RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient = + new RestClientAuthorizationCodeTokenResponseClient(); + accessTokenResponseClient.addParametersConverter(parametersConverter()); http .authorizeHttpRequests((authorize) -> authorize @@ -1600,11 +1589,8 @@ class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter() - requestEntityConverter.addParametersConverter(parametersConverter()) - - val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient() - tokenResponseClient.setRequestEntityConverter(requestEntityConverter) + val tokenResponseClient = RestClientAuthorizationCodeTokenResponseClient() + tokenResponseClient.addParametersConverter(parametersConverter()) http { authorizeHttpRequests { @@ -1648,13 +1634,9 @@ public class SecurityConfig { @Bean public OAuth2AccessTokenResponseClient clientCredentialsAccessTokenResponseClient() { - OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter = - new OAuth2ClientCredentialsGrantRequestEntityConverter(); - requestEntityConverter.addParametersConverter(parametersConverter()); - - DefaultClientCredentialsTokenResponseClient accessTokenResponseClient = - new DefaultClientCredentialsTokenResponseClient(); - accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter); + RestClientClientCredentialsTokenResponseClient accessTokenResponseClient = + new RestClientClientCredentialsTokenResponseClient(); + accessTokenResponseClient.addParametersConverter(parametersConverter()); return accessTokenResponseClient; } @@ -1675,11 +1657,8 @@ class SecurityConfig { @Bean fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient { - val requestEntityConverter = OAuth2ClientCredentialsGrantRequestEntityConverter() - requestEntityConverter.addParametersConverter(parametersConverter()) - - val accessTokenResponseClient = DefaultClientCredentialsTokenResponseClient() - accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter) + val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient() + accessTokenResponseClient.addParametersConverter(parametersConverter()) return accessTokenResponseClient } @@ -1694,11 +1673,11 @@ class SecurityConfig { Spring Security automatically resolves the following generic types of `OAuth2AccessTokenResponseClient` beans: -* `OAuth2AuthorizationCodeGrantRequest` (see `DefaultAuthorizationCodeTokenResponseClient`) -* `OAuth2RefreshTokenGrantRequest` (see `DefaultRefreshTokenTokenResponseClient`) -* `OAuth2ClientCredentialsGrantRequest` (see `DefaultClientCredentialsTokenResponseClient`) -* `JwtBearerGrantRequest` (see `DefaultJwtBearerTokenResponseClient`) -* `TokenExchangeGrantRequest` (see `DefaultTokenExchangeTokenResponseClient`) +* `OAuth2AuthorizationCodeGrantRequest` (see `RestClientAuthorizationCodeTokenResponseClient`) +* `OAuth2RefreshTokenGrantRequest` (see `RestClientRefreshTokenTokenResponseClient`) +* `OAuth2ClientCredentialsGrantRequest` (see `RestClientClientCredentialsTokenResponseClient`) +* `JwtBearerGrantRequest` (see `RestClientJwtBearerTokenResponseClient`) +* `TokenExchangeGrantRequest` (see `RestClientTokenExchangeTokenResponseClient`) [TIP] ==== @@ -1710,17 +1689,17 @@ Publishing a bean of type `OAuth2AccessTokenResponseClient` will automatically enable the `token-exchange` grant type without the need to <>. ==== -[[oauth2-client-customize-rest-operations]] -=== Customize the `RestOperations` used by OAuth2 Client Components +[[oauth2-client-customize-rest-client]] +=== Customize the `RestClient` used by OAuth2 Client Components -Another common use case is the need to customize the `RestOperations` used when obtaining an access token. +Another common use case is the need to customize the `RestClient` used when obtaining an access token. We might need to do this to customize processing of the response (via a custom `HttpMessageConverter`) or to apply proxy settings for a corporate network (via a customized `ClientHttpRequestFactory`). With Spring Security 6.2 and later, we can simply publish beans of type `OAuth2AccessTokenResponseClient` and Spring Security will configure and publish an `OAuth2AuthorizedClientManager` bean for us. -The following example customizes the `RestOperations` for all of the supported grant types: +The following example customizes the `RestClient` for all of the supported grant types: -.Customize `RestOperations` for OAuth2 Client +.Customize `RestClient` for OAuth2 Client [tabs] ===== Java:: @@ -1732,51 +1711,51 @@ public class SecurityConfig { @Bean public OAuth2AccessTokenResponseClient authorizationCodeAccessTokenResponseClient() { - DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient = - new DefaultAuthorizationCodeTokenResponseClient(); - accessTokenResponseClient.setRestOperations(restTemplate()); + RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient = + new RestClientAuthorizationCodeTokenResponseClient(); + accessTokenResponseClient.setRestClient(restClient()); return accessTokenResponseClient; } @Bean public OAuth2AccessTokenResponseClient refreshTokenAccessTokenResponseClient() { - DefaultRefreshTokenTokenResponseClient accessTokenResponseClient = - new DefaultRefreshTokenTokenResponseClient(); - accessTokenResponseClient.setRestOperations(restTemplate()); + RestClientRefreshTokenTokenResponseClient accessTokenResponseClient = + new RestClientRefreshTokenTokenResponseClient(); + accessTokenResponseClient.setRestClient(restClient()); return accessTokenResponseClient; } @Bean public OAuth2AccessTokenResponseClient clientCredentialsAccessTokenResponseClient() { - DefaultClientCredentialsTokenResponseClient accessTokenResponseClient = - new DefaultClientCredentialsTokenResponseClient(); - accessTokenResponseClient.setRestOperations(restTemplate()); + RestClientClientCredentialsTokenResponseClient accessTokenResponseClient = + new RestClientClientCredentialsTokenResponseClient(); + accessTokenResponseClient.setRestClient(restClient()); return accessTokenResponseClient; } @Bean public OAuth2AccessTokenResponseClient jwtBearerAccessTokenResponseClient() { - DefaultJwtBearerTokenResponseClient accessTokenResponseClient = - new DefaultJwtBearerTokenResponseClient(); - accessTokenResponseClient.setRestOperations(restTemplate()); + RestClientJwtBearerTokenResponseClient accessTokenResponseClient = + new RestClientJwtBearerTokenResponseClient(); + accessTokenResponseClient.setRestClient(restClient()); return accessTokenResponseClient; } @Bean public OAuth2AccessTokenResponseClient tokenExchangeAccessTokenResponseClient() { - DefaultTokenExchangeTokenResponseClient accessTokenResponseClient = - new DefaultTokenExchangeTokenResponseClient(); - accessTokenResponseClient.setRestOperations(restTemplate()); + RestClientTokenExchangeTokenResponseClient accessTokenResponseClient = + new RestClientTokenExchangeTokenResponseClient(); + accessTokenResponseClient.setRestClient(restClient()); return accessTokenResponseClient; } @Bean - public RestTemplate restTemplate() { + public RestClient restClient() { // ... } @@ -1792,46 +1771,46 @@ class SecurityConfig { @Bean fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient { - val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient() - accessTokenResponseClient.setRestOperations(restTemplate()) + val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient() + accessTokenResponseClient.setRestClient(restClient()) return accessTokenResponseClient } @Bean fun refreshTokenAccessTokenResponseClient(): OAuth2AccessTokenResponseClient { - val accessTokenResponseClient = DefaultRefreshTokenTokenResponseClient() - accessTokenResponseClient.setRestOperations(restTemplate()) + val accessTokenResponseClient = RestClientRefreshTokenTokenResponseClient() + accessTokenResponseClient.setRestClient(restClient()) return accessTokenResponseClient } @Bean fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient { - val accessTokenResponseClient = DefaultClientCredentialsTokenResponseClient() - accessTokenResponseClient.setRestOperations(restTemplate()) + val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient() + accessTokenResponseClient.setRestClient(restClient()) return accessTokenResponseClient } @Bean fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient { - val accessTokenResponseClient = DefaultJwtBearerTokenResponseClient() - accessTokenResponseClient.setRestOperations(restTemplate()) + val accessTokenResponseClient = RestClientJwtBearerTokenResponseClient() + accessTokenResponseClient.setRestClient(restClient()) return accessTokenResponseClient } @Bean fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient { - val accessTokenResponseClient = DefaultTokenExchangeTokenResponseClient() - accessTokenResponseClient.setRestOperations(restTemplate()) + val accessTokenResponseClient = RestClientTokenExchangeTokenResponseClient() + accessTokenResponseClient.setRestClient(restClient()) return accessTokenResponseClient } @Bean - fun restTemplate(): RestTemplate { + fun restClient(): RestClient { // ... } @@ -1851,7 +1830,7 @@ Prior to Spring Security 6.2, we had to ensure this customization was applied to We had to use both the Spring Security DSL (for the `authorization_code` grant) and publish a bean of type `OAuth2AuthorizedClientManager` for other grant types. To understand what is being configured behind the scenes, here's what the configuration might have looked like: -.Customize `RestOperations` for OAuth2 Client (prior to 6.2) +.Customize `RestClient` for OAuth2 Client (prior to 6.2) [tabs] ===== Java:: @@ -1864,9 +1843,9 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient = - new DefaultAuthorizationCodeTokenResponseClient(); - accessTokenResponseClient.setRestOperations(restTemplate()); + RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient = + new RestClientAuthorizationCodeTokenResponseClient(); + accessTokenResponseClient.setRestClient(restClient()); http // ... @@ -1889,25 +1868,25 @@ public class SecurityConfig { ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { - DefaultRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient = - new DefaultRefreshTokenTokenResponseClient(); - refreshTokenAccessTokenResponseClient.setRestOperations(restTemplate()); + RestClientRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient = + new RestClientRefreshTokenTokenResponseClient(); + refreshTokenAccessTokenResponseClient.setRestClient(restClient()); - DefaultClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient = - new DefaultClientCredentialsTokenResponseClient(); - clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate()); + RestClientClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient = + new RestClientClientCredentialsTokenResponseClient(); + clientCredentialsAccessTokenResponseClient.setRestClient(restClient()); - DefaultJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient = - new DefaultJwtBearerTokenResponseClient(); - jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate()); + RestClientJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient = + new RestClientJwtBearerTokenResponseClient(); + jwtBearerAccessTokenResponseClient.setRestClient(restClient()); JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider(); jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient); - DefaultTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient = - new DefaultTokenExchangeTokenResponseClient(); - tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate()); + RestClientTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient = + new RestClientTokenExchangeTokenResponseClient(); + tokenExchangeAccessTokenResponseClient.setRestClient(restClient()); TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider(); @@ -1935,7 +1914,7 @@ public class SecurityConfig { } @Bean - public RestTemplate restTemplate() { + public RestClient restClient() { // ... } @@ -1954,8 +1933,8 @@ class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient() - tokenResponseClient.setRestOperations(restTemplate()) + val tokenResponseClient = RestClientAuthorizationCodeTokenResponseClient() + tokenResponseClient.setRestClient(restClient()) http { // ... @@ -1979,20 +1958,20 @@ class SecurityConfig { clientRegistrationRepository: ClientRegistrationRepository?, authorizedClientRepository: OAuth2AuthorizedClientRepository? ): OAuth2AuthorizedClientManager { - val refreshTokenAccessTokenResponseClient = DefaultRefreshTokenTokenResponseClient() - refreshTokenAccessTokenResponseClient.setRestOperations(restTemplate()) + val refreshTokenAccessTokenResponseClient = RestClientRefreshTokenTokenResponseClient() + refreshTokenAccessTokenResponseClient.setRestClient(restClient()) - val clientCredentialsAccessTokenResponseClient = DefaultClientCredentialsTokenResponseClient() - clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate()) + val clientCredentialsAccessTokenResponseClient = RestClientClientCredentialsTokenResponseClient() + clientCredentialsAccessTokenResponseClient.setRestClient(restClient()) - val jwtBearerAccessTokenResponseClient = DefaultJwtBearerTokenResponseClient() - jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate()) + val jwtBearerAccessTokenResponseClient = RestClientJwtBearerTokenResponseClient() + jwtBearerAccessTokenResponseClient.setRestClient(restClient()) val jwtBearerAuthorizedClientProvider = JwtBearerOAuth2AuthorizedClientProvider() jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient) - val tokenExchangeAccessTokenResponseClient = DefaultTokenExchangeTokenResponseClient() - tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate()) + val tokenExchangeAccessTokenResponseClient = RestClientTokenExchangeTokenResponseClient() + tokenExchangeAccessTokenResponseClient.setRestClient(restClient()) val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider() tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient) @@ -2018,7 +1997,7 @@ class SecurityConfig { } @Bean - fun restTemplate(): RestTemplate { + fun restClient(): RestClient { // ... } diff --git a/docs/modules/ROOT/partials/servlet/oauth2/client/rest-client-access-token-response-client.adoc b/docs/modules/ROOT/partials/servlet/oauth2/client/rest-client-access-token-response-client.adoc index 6ea3eb3d23..67f402b9dc 100644 --- a/docs/modules/ROOT/partials/servlet/oauth2/client/rest-client-access-token-response-client.adoc +++ b/docs/modules/ROOT/partials/servlet/oauth2/client/rest-client-access-token-response-client.adoc @@ -1,35 +1,3 @@ -To opt-in to using `{class-name}`, simply provide a bean as in the following example and it will be picked up by the default `OAuth2AuthorizedClientManager` automatically: - -[#oauth2-client-{section-id}-access-token-response-client-bean] -.Access Token Response Configuration -[tabs] -====== -Java:: -+ -[source,java,role="primary",subs="+attributes"] ----- -@Bean -public OAuth2AccessTokenResponseClient<{grant-request}> accessTokenResponseClient() { - return new {class-name}(); -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary",subs="+attributes"] ----- -@Bean -fun accessTokenResponseClient(): OAuth2AccessTokenResponseClient<{grant-type}> { - return {class-name}() -} ----- -====== - -[NOTE] -==== -The new implementation will be the default in Spring Security 7. -==== - `{class-name}` is very flexible and provides several options for customizing the OAuth 2.0 Access Token request and response for the {grant-type} grant. Choose from the following use cases to learn more: diff --git a/etc/checkstyle/checkstyle-suppressions.xml b/etc/checkstyle/checkstyle-suppressions.xml index 6bc4e30592..4c9e12f670 100644 --- a/etc/checkstyle/checkstyle-suppressions.xml +++ b/etc/checkstyle/checkstyle-suppressions.xml @@ -34,7 +34,6 @@ - diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProvider.java index 0a5a88ba04..2904cd639a 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * 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. @@ -21,9 +21,9 @@ import java.time.Duration; import java.time.Instant; import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; +import org.springframework.security.oauth2.client.endpoint.RestClientClientCredentialsTokenResponseClient; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; @@ -38,11 +38,11 @@ import org.springframework.util.Assert; * @author Joe Grandja * @since 5.2 * @see OAuth2AuthorizedClientProvider - * @see DefaultClientCredentialsTokenResponseClient + * @see RestClientClientCredentialsTokenResponseClient */ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider { - private OAuth2AccessTokenResponseClient accessTokenResponseClient = new DefaultClientCredentialsTokenResponseClient(); + private OAuth2AccessTokenResponseClient accessTokenResponseClient = new RestClientClientCredentialsTokenResponseClient(); private Duration clockSkew = Duration.ofSeconds(60); diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JwtBearerOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JwtBearerOAuth2AuthorizedClientProvider.java index 857f38af0b..2fbbabb7c6 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JwtBearerOAuth2AuthorizedClientProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JwtBearerOAuth2AuthorizedClientProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -22,9 +22,9 @@ import java.time.Instant; import java.util.function.Function; import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.client.endpoint.DefaultJwtBearerTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.RestClientJwtBearerTokenResponseClient; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; @@ -40,11 +40,11 @@ import org.springframework.util.Assert; * @author Joe Grandja * @since 5.5 * @see OAuth2AuthorizedClientProvider - * @see DefaultJwtBearerTokenResponseClient + * @see RestClientJwtBearerTokenResponseClient */ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider { - private OAuth2AccessTokenResponseClient accessTokenResponseClient = new DefaultJwtBearerTokenResponseClient(); + private OAuth2AccessTokenResponseClient accessTokenResponseClient = new RestClientJwtBearerTokenResponseClient(); private Function jwtAssertionResolver = this::resolveJwtAssertion; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProvider.java index e306c7e9fe..652943cdd0 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProvider.java @@ -27,9 +27,9 @@ import java.util.Set; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; +import org.springframework.security.oauth2.client.endpoint.RestClientRefreshTokenTokenResponseClient; import org.springframework.security.oauth2.client.event.OAuth2AuthorizedClientRefreshedEvent; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; @@ -44,12 +44,12 @@ import org.springframework.util.Assert; * @author Joe Grandja * @since 5.2 * @see OAuth2AuthorizedClientProvider - * @see DefaultRefreshTokenTokenResponseClient + * @see RestClientRefreshTokenTokenResponseClient */ public final class RefreshTokenOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider, ApplicationEventPublisherAware { - private OAuth2AccessTokenResponseClient accessTokenResponseClient = new DefaultRefreshTokenTokenResponseClient(); + private OAuth2AccessTokenResponseClient accessTokenResponseClient = new RestClientRefreshTokenTokenResponseClient(); private ApplicationEventPublisher applicationEventPublisher; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java index ca22416af9..0407eb08e1 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * 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. @@ -22,8 +22,8 @@ import java.time.Instant; import java.util.function.Function; import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.client.endpoint.DefaultTokenExchangeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.RestClientTokenExchangeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -39,11 +39,11 @@ import org.springframework.util.Assert; * @author Steve Riesenberg * @since 6.3 * @see OAuth2AuthorizedClientProvider - * @see DefaultTokenExchangeTokenResponseClient + * @see RestClientTokenExchangeTokenResponseClient */ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider { - private OAuth2AccessTokenResponseClient accessTokenResponseClient = new DefaultTokenExchangeTokenResponseClient(); + private OAuth2AccessTokenResponseClient accessTokenResponseClient = new RestClientTokenExchangeTokenResponseClient(); private Function subjectTokenResolver = this::resolveSubjectToken; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractOAuth2AuthorizationGrantRequestEntityConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractOAuth2AuthorizationGrantRequestEntityConverter.java deleted file mode 100644 index 60e29fe168..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractOAuth2AuthorizationGrantRequestEntityConverter.java +++ /dev/null @@ -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 the type of {@link AbstractOAuth2AuthorizationGrantRequest} - * @author Joe Grandja - * @since 5.5 - * @see Converter - * @see AbstractOAuth2AuthorizationGrantRequest - * @see RequestEntity - */ -abstract class AbstractOAuth2AuthorizationGrantRequestEntityConverter - implements Converter> { - - private Converter headersConverter = DefaultOAuth2TokenRequestHeadersConverter.withCharsetUtf8(); - - private Converter> parametersConverter = this::createParameters; - - @Override - public RequestEntity convert(T authorizationGrantRequest) { - HttpHeaders headers = getHeadersConverter().convert(authorizationGrantRequest); - MultiValueMap 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 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 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 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 headersConverter) { - Assert.notNull(headersConverter, "headersConverter cannot be null"); - Converter 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> 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> 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> parametersConverter) { - Assert.notNull(parametersConverter, "parametersConverter cannot be null"); - Converter> currentParametersConverter = this.parametersConverter; - this.parametersConverter = (authorizationGrantRequest) -> { - // Append parameters using a Composite Converter - MultiValueMap parameters = currentParametersConverter.convert(authorizationGrantRequest); - if (parameters == null) { - parameters = new LinkedMultiValueMap<>(); - } - MultiValueMap parametersToAdd = parametersConverter.convert(authorizationGrantRequest); - if (parametersToAdd != null) { - parameters.addAll(parametersToAdd); - } - return parameters; - }; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/ClientAuthenticationMethodValidatingRequestEntityConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/ClientAuthenticationMethodValidatingRequestEntityConverter.java deleted file mode 100644 index 1c6068b08b..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/ClientAuthenticationMethodValidatingRequestEntityConverter.java +++ /dev/null @@ -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 - implements Converter> { - - private final Converter> delegate; - - ClientAuthenticationMethodValidatingRequestEntityConverter(Converter> 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); - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java deleted file mode 100644 index 9e76af76b9..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClient.java +++ /dev/null @@ -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 Section 4.1.3 Access Token Request - * (Authorization Code Grant) - * @see Section 4.1.4 Access Token Response - * (Authorization Code Grant) - * @deprecated Use {@link RestClientAuthorizationCodeTokenResponseClient} instead - */ -@Deprecated(since = "6.4", forRemoval = true) -public final class DefaultAuthorizationCodeTokenResponseClient - implements OAuth2AccessTokenResponseClient { - - private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; - - private Converter> 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 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 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> 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. - * - *

- * NOTE: At a minimum, the supplied {@code restOperations} must be configured - * with the following: - *

    - *
  1. {@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and - * {@link OAuth2AccessTokenResponseHttpMessageConverter}
  2. - *
  3. {@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}
  4. - *
- * @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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java deleted file mode 100644 index a5338dca18..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClient.java +++ /dev/null @@ -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 Section 4.4.2 Access Token Request - * (Client Credentials Grant) - * @see Section 4.4.3 Access Token Response - * (Client Credentials Grant) - * @deprecated Use {@link RestClientClientCredentialsTokenResponseClient} instead - */ -@Deprecated(since = "6.4", forRemoval = true) -public final class DefaultClientCredentialsTokenResponseClient - implements OAuth2AccessTokenResponseClient { - - private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; - - private Converter> 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 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 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> 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. - * - *

- * NOTE: At a minimum, the supplied {@code restOperations} must be configured - * with the following: - *

    - *
  1. {@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and - * {@link OAuth2AccessTokenResponseHttpMessageConverter}
  2. - *
  3. {@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}
  4. - *
- * @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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClient.java deleted file mode 100644 index f4e925a57d..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClient.java +++ /dev/null @@ -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 Section - * 2.1 Using JWTs as Authorization Grants - * @see Section - * 4.1 Using Assertions as Authorization Grants - * @deprecated Use {@link RestClientJwtBearerTokenResponseClient} instead - */ -@Deprecated(since = "6.4", forRemoval = true) -public final class DefaultJwtBearerTokenResponseClient - implements OAuth2AccessTokenResponseClient { - - private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; - - private Converter> 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 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 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> 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. - * - *

- * NOTE: At a minimum, the supplied {@code restOperations} must be configured - * with the following: - *

    - *
  1. {@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and - * {@link OAuth2AccessTokenResponseHttpMessageConverter}
  2. - *
  3. {@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}
  4. - *
- * @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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java index 40419e1143..a8464718c8 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverter.java @@ -36,14 +36,12 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod; * @author Peter Eastham * @author Steve Riesenberg * @since 6.3 - * @see AbstractOAuth2AuthorizationGrantRequestEntityConverter + * @see AbstractRestClientOAuth2AccessTokenResponseClient + * @see AbstractWebClientReactiveOAuth2AccessTokenResponseClient */ public final class DefaultOAuth2TokenRequestHeadersConverter implements Converter { - private static final MediaType APPLICATION_FORM_URLENCODED_UTF8 = new MediaType( - MediaType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8); - private List accept = List.of(MediaType.APPLICATION_JSON); private MediaType contentType = MediaType.APPLICATION_FORM_URLENCODED; @@ -89,17 +87,4 @@ public final class DefaultOAuth2TokenRequestHeadersConverter DefaultOAuth2TokenRequestHeadersConverter withCharsetUtf8() { - DefaultOAuth2TokenRequestHeadersConverter converter = new DefaultOAuth2TokenRequestHeadersConverter<>(); - converter.contentType = APPLICATION_FORM_URLENCODED_UTF8; - return converter; - } - } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClient.java deleted file mode 100644 index 924df457fa..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClient.java +++ /dev/null @@ -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 Section 6 - * Refreshing an Access Token - * @deprecated Use {@link RestClientRefreshTokenTokenResponseClient} instead - */ -@Deprecated(since = "6.4", forRemoval = true) -public final class DefaultRefreshTokenTokenResponseClient - implements OAuth2AccessTokenResponseClient { - - private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; - - private Converter> 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 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 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> 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. - * - *

- * NOTE: At a minimum, the supplied {@code restOperations} must be configured - * with the following: - *

    - *
  1. {@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and - * {@link OAuth2AccessTokenResponseHttpMessageConverter}
  2. - *
  3. {@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}
  4. - *
- * @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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClient.java deleted file mode 100644 index 4d42d37ca1..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClient.java +++ /dev/null @@ -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 Section - * 2.1 Request - * @see Section - * 2.2 Response - * @deprecated Use {@link RestClientRefreshTokenTokenResponseClient} instead - */ -@Deprecated(since = "6.4", forRemoval = true) -public final class DefaultTokenExchangeTokenResponseClient - implements OAuth2AccessTokenResponseClient { - - private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; - - private Converter> 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 responseEntity = getResponse(requestEntity); - - return responseEntity.getBody(); - } - - private ResponseEntity 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> 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. - * - *

- * NOTE: At a minimum, the supplied {@code restOperations} must be configured - * with the following: - *

    - *
  1. {@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and - * {@link OAuth2AccessTokenResponseHttpMessageConverter}
  2. - *
  3. {@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}
  4. - *
- * @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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverter.java deleted file mode 100644 index 6486179b92..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverter.java +++ /dev/null @@ -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 Section - * 2.1 Using JWTs as Authorization Grants - * @deprecated Use {@link DefaultOAuth2TokenRequestParametersConverter} instead - */ -@Deprecated(since = "6.4", forRemoval = true) -public class JwtBearerGrantRequestEntityConverter - extends AbstractOAuth2AuthorizationGrantRequestEntityConverter { - - @Override - protected MultiValueMap createParameters(JwtBearerGrantRequest jwtBearerGrantRequest) { - ClientRegistration clientRegistration = jwtBearerGrantRequest.getClientRegistration(); - MultiValueMap 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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusJwtClientAuthenticationParametersConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusJwtClientAuthenticationParametersConverter.java index 8b19a0a830..e7a9eff28e 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusJwtClientAuthenticationParametersConverter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusJwtClientAuthenticationParametersConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -67,8 +67,8 @@ import org.springframework.util.MultiValueMap; * @since 5.5 * @see Converter * @see com.nimbusds.jose.jwk.JWK - * @see OAuth2AuthorizationCodeGrantRequestEntityConverter#addParametersConverter(Converter) - * @see OAuth2ClientCredentialsGrantRequestEntityConverter#addParametersConverter(Converter) + * @see RestClientAuthorizationCodeTokenResponseClient#addParametersConverter(Converter) + * @see RestClientClientCredentialsTokenResponseClient#addParametersConverter(Converter) * @see 2.2 * Using JWTs for Client Authentication * @see 4.2 diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverter.java deleted file mode 100644 index 15e021dc14..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverter.java +++ /dev/null @@ -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 { - - @Override - protected MultiValueMap createParameters( - OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) { - ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration(); - OAuth2AuthorizationExchange authorizationExchange = authorizationCodeGrantRequest.getAuthorizationExchange(); - MultiValueMap 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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverter.java deleted file mode 100644 index 4ac1a69a3b..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverter.java +++ /dev/null @@ -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 { - - @Override - protected MultiValueMap createParameters( - OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) { - ClientRegistration clientRegistration = clientCredentialsGrantRequest.getClientRegistration(); - MultiValueMap 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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverter.java deleted file mode 100644 index b8f1d7f391..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverter.java +++ /dev/null @@ -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 { - - @Override - protected MultiValueMap createParameters(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) { - ClientRegistration clientRegistration = refreshTokenGrantRequest.getClientRegistration(); - MultiValueMap 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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverter.java deleted file mode 100644 index 142ef27225..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverter.java +++ /dev/null @@ -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 Section - * 1.1 Delegation vs. Impersonation Semantics - * @deprecated Use {@link DefaultOAuth2TokenRequestParametersConverter} instead - */ -@Deprecated(since = "6.4", forRemoval = true) -public class TokenExchangeGrantRequestEntityConverter - extends AbstractOAuth2AuthorizationGrantRequestEntityConverter { - - 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 createParameters(TokenExchangeGrantRequest grantRequest) { - ClientRegistration clientRegistration = grantRequest.getClientRegistration(); - MultiValueMap 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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java index 98c4370e25..597d063164 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java @@ -482,7 +482,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement OAuth2AuthorizeRequest authorizeRequest = builder.build(); // NOTE: 'authorizedClientManager.authorize()' needs to be executed on a dedicated // thread via subscribeOn(Schedulers.boundedElastic()) since it performs a - // blocking I/O operation using RestTemplate internally + // blocking I/O operation using RestClient internally return Mono.fromSupplier(() -> this.authorizedClientManager.authorize(authorizeRequest)) .subscribeOn(Schedulers.boundedElastic()); } @@ -505,7 +505,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement OAuth2AuthorizeRequest reauthorizeRequest = builder.build(); // NOTE: 'authorizedClientManager.authorize()' needs to be executed on a dedicated // thread via subscribeOn(Schedulers.boundedElastic()) since it performs a - // blocking I/O operation using RestTemplate internally + // blocking I/O operation using RestClient internally return Mono.fromSupplier(() -> this.authorizedClientManager.authorize(reauthorizeRequest)) .subscribeOn(Schedulers.boundedElastic()); } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java deleted file mode 100644 index 57cdd38b18..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultAuthorizationCodeTokenResponseClientTests.java +++ /dev/null @@ -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 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 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 jwkResolver) { - NimbusJwtClientAuthenticationParametersConverter 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); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java deleted file mode 100644 index 96c6de171c..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultClientCredentialsTokenResponseClientTests.java +++ /dev/null @@ -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 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 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 jwkResolver) { - NimbusJwtClientAuthenticationParametersConverter 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); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java deleted file mode 100644 index d2a95f6418..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultJwtBearerTokenResponseClientTests.java +++ /dev/null @@ -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); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverterTests.java index f264679796..f0f8372658 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestHeadersConverterTests.java @@ -78,39 +78,4 @@ public class DefaultOAuth2TokenRequestHeadersConverterTests { .isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0PQ=="); } - @Test - public void convertWhenWithCharsetUtf8AndEncodeClientCredentialsTrueThenConvertsWithUrlEncoding() { - this.converter = DefaultOAuth2TokenRequestHeadersConverter.withCharsetUtf8(); - // @formatter:off - ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials() - .clientId("clientId") - .clientSecret("clientSecret=") - .build(); - // @formatter:on - OAuth2ClientCredentialsGrantRequest grantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration); - HttpHeaders defaultHeaders = this.converter.convert(grantRequest); - assertThat(defaultHeaders.getAccept()).containsExactly(MediaType.APPLICATION_JSON); - assertThat(defaultHeaders.getContentType()).isEqualTo(APPLICATION_FORM_URLENCODED_UTF8); - assertThat(defaultHeaders.getFirst(HttpHeaders.AUTHORIZATION)) - .isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0JTNE"); - } - - @Test - public void convertWhenWithCharsetUtf8EncodeClientCredentialsFalseThenConvertsWithoutUrlEncoding() { - this.converter = DefaultOAuth2TokenRequestHeadersConverter.withCharsetUtf8(); - this.converter.setEncodeClientCredentials(false); - // @formatter:off - ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials() - .clientId("clientId") - .clientSecret("clientSecret=") - .build(); - // @formatter:on - OAuth2ClientCredentialsGrantRequest grantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration); - HttpHeaders defaultHeaders = this.converter.convert(grantRequest); - assertThat(defaultHeaders.getAccept()).containsExactly(MediaType.APPLICATION_JSON); - assertThat(defaultHeaders.getContentType()).isEqualTo(APPLICATION_FORM_URLENCODED_UTF8); - assertThat(defaultHeaders.getFirst(HttpHeaders.AUTHORIZATION)) - .isEqualTo("Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0PQ=="); - } - } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java deleted file mode 100644 index c1ee06fcd3..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultRefreshTokenTokenResponseClientTests.java +++ /dev/null @@ -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 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 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 jwkResolver) { - NimbusJwtClientAuthenticationParametersConverter 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); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClientTests.java deleted file mode 100644 index 4d0afc9163..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultTokenExchangeTokenResponseClientTests.java +++ /dev/null @@ -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> 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)); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java deleted file mode 100644 index d228e2bd8a..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/JwtBearerGrantRequestEntityConverterTests.java +++ /dev/null @@ -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 headersConverter1 = mock(Converter.class); - this.converter.setHeadersConverter(headersConverter1); - Converter 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> parametersConverter1 = mock(Converter.class); - this.converter.setParametersConverter(parametersConverter1); - Converter> 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 formParameters = (MultiValueMap) 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"); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java deleted file mode 100644 index a90201938b..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java +++ /dev/null @@ -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 headersConverter1 = mock(Converter.class); - this.converter.setHeadersConverter(headersConverter1); - Converter 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> parametersConverter1 = mock( - Converter.class); - this.converter.setParametersConverter(parametersConverter1); - Converter> 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 formParameters = (MultiValueMap) 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 attributes = new HashMap<>(); - attributes.put(PkceParameterNames.CODE_VERIFIER, "code-verifier-1234"); - Map 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 formParameters = (MultiValueMap) 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)); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java deleted file mode 100644 index 91d9e36c1c..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java +++ /dev/null @@ -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 headersConverter1 = mock(Converter.class); - this.converter.setHeadersConverter(headersConverter1); - Converter 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> parametersConverter1 = mock( - Converter.class); - this.converter.setParametersConverter(parametersConverter1); - Converter> 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 formParameters = (MultiValueMap) 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 formParameters = (MultiValueMap) requestEntity.getBody(); - assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) - .isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes()); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java deleted file mode 100644 index 959b92fbb0..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2RefreshTokenGrantRequestEntityConverterTests.java +++ /dev/null @@ -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 headersConverter1 = mock(Converter.class); - this.converter.setHeadersConverter(headersConverter1); - Converter 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> parametersConverter1 = mock( - Converter.class); - this.converter.setParametersConverter(parametersConverter1); - Converter> 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 formParameters = (MultiValueMap) 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"); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverterTests.java deleted file mode 100644 index 38c2c29e6f..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequestEntityConverterTests.java +++ /dev/null @@ -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 headersConverter1 = mock(Converter.class); - this.converter.setHeadersConverter(headersConverter1); - Converter 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> parametersConverter1 = mock( - Converter.class); - this.converter.setParametersConverter(parametersConverter1); - Converter> 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 formParameters = (MultiValueMap) 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 formParameters = (MultiValueMap) 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 formParameters = (MultiValueMap) 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 formParameters = (MultiValueMap) 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 formParameters = (MultiValueMap) 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(), " ")); - } - -}