diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/RestClientSpringOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/RestClientOpaqueTokenIntrospector.java similarity index 85% rename from oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/RestClientSpringOpaqueTokenIntrospector.java rename to oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/RestClientOpaqueTokenIntrospector.java index a3fe718dc0..cda1cb1d60 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/RestClientSpringOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/RestClientOpaqueTokenIntrospector.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -59,7 +60,7 @@ import org.springframework.web.client.RestClient; * @author Andrey Litvitski * @since 7.1 */ -public class RestClientSpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector { +public class RestClientOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private static final String AUTHORITY_PREFIX = "SCOPE_"; @@ -81,7 +82,7 @@ public class RestClientSpringOpaqueTokenIntrospector implements OpaqueTokenIntro * @param introspectionUri The introspection endpoint uri * @param restClient The client for performing the introspection request */ - public RestClientSpringOpaqueTokenIntrospector(String introspectionUri, RestClient restClient) { + public RestClientOpaqueTokenIntrospector(String introspectionUri, RestClient restClient) { Assert.notNull(introspectionUri, "introspectionUri cannot be null"); Assert.notNull(restClient, "restClient cannot be null"); this.requestEntityConverter = this.defaultRequestEntityConverter(URI.create(introspectionUri)); @@ -214,14 +215,13 @@ public class RestClientSpringOpaqueTokenIntrospector implements OpaqueTokenIntro *

* Sets the {@link Converter Converter<OAuth2TokenIntrospectionClaimAccessor, * OAuth2AuthenticatedPrincipal>} to use. Defaults to - * {@link RestClientSpringOpaqueTokenIntrospector#defaultAuthenticationConverter}. + * {@link RestClientOpaqueTokenIntrospector#defaultAuthenticationConverter}. *

*

* Use if you need a custom mapping of OAuth 2.0 token claims to the authenticated * principal. *

* @param authenticationConverter the converter - * @since 7.1 */ public void setAuthenticationConverter( Converter authenticationConverter) { @@ -230,14 +230,13 @@ public class RestClientSpringOpaqueTokenIntrospector implements OpaqueTokenIntro } /** - * If {@link RestClientSpringOpaqueTokenIntrospector#authenticationConverter} is not + * If {@link RestClientOpaqueTokenIntrospector#authenticationConverter} is not * explicitly set, this default converter will be used. transforms an * {@link OAuth2TokenIntrospectionClaimAccessor} into an * {@link OAuth2AuthenticatedPrincipal} by extracting claims, mapping scopes to * authorities, and creating a principal. * @return {@link Converter Converter<OAuth2TokenIntrospectionClaimAccessor, * OAuth2AuthenticatedPrincipal>} - * @since 7.1 */ private OAuth2IntrospectionAuthenticatedPrincipal defaultAuthenticationConverter( OAuth2TokenIntrospectionClaimAccessor accessor) { @@ -257,15 +256,14 @@ public class RestClientSpringOpaqueTokenIntrospector implements OpaqueTokenIntro } /** - * Creates a {@code RestClientSpringOpaqueTokenIntrospector.Builder} with the given + * Creates a {@code RestClientOpaqueTokenIntrospector.Builder} with the given * introspection endpoint uri * @param introspectionUri The introspection endpoint uri - * @return the {@link RestClientSpringOpaqueTokenIntrospector.Builder} - * @since 7.1 + * @return the {@link RestClientOpaqueTokenIntrospector.Builder} */ - public static RestClientSpringOpaqueTokenIntrospector.Builder withIntrospectionUri(String introspectionUri) { + public static RestClientOpaqueTokenIntrospector.Builder withIntrospectionUri(String introspectionUri) { Assert.notNull(introspectionUri, "introspectionUri cannot be null"); - return new RestClientSpringOpaqueTokenIntrospector.Builder(introspectionUri); + return new RestClientOpaqueTokenIntrospector.Builder(introspectionUri); } // gh-7563 @@ -295,7 +293,7 @@ public class RestClientSpringOpaqueTokenIntrospector implements OpaqueTokenIntro } /** - * Used to build {@link RestClientSpringOpaqueTokenIntrospector}. + * Used to build {@link RestClientOpaqueTokenIntrospector}. * * @author Andrey Litvitski * @since 7.1 @@ -308,6 +306,8 @@ public class RestClientSpringOpaqueTokenIntrospector implements OpaqueTokenIntro private String clientSecret; + private final List> postProcessors = new ArrayList<>(); + private Builder(String introspectionUri) { this.introspectionUri = introspectionUri; } @@ -316,10 +316,9 @@ public class RestClientSpringOpaqueTokenIntrospector implements OpaqueTokenIntro * The builder will {@link URLEncoder encode} the client id that you provide, so * please give the unencoded value. * @param clientId The unencoded client id - * @return the {@link RestClientSpringOpaqueTokenIntrospector.Builder} - * @since 7.1 + * @return the {@link RestClientOpaqueTokenIntrospector.Builder} */ - public RestClientSpringOpaqueTokenIntrospector.Builder clientId(String clientId) { + public RestClientOpaqueTokenIntrospector.Builder clientId(String clientId) { Assert.notNull(clientId, "clientId cannot be null"); this.clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8); return this; @@ -329,25 +328,39 @@ public class RestClientSpringOpaqueTokenIntrospector implements OpaqueTokenIntro * The builder will {@link URLEncoder encode} the client secret that you provide, * so please give the unencoded value. * @param clientSecret The unencoded client secret - * @return the {@link RestClientSpringOpaqueTokenIntrospector.Builder} - * @since 7.1 + * @return the {@link RestClientOpaqueTokenIntrospector.Builder} */ - public RestClientSpringOpaqueTokenIntrospector.Builder clientSecret(String clientSecret) { + public RestClientOpaqueTokenIntrospector.Builder clientSecret(String clientSecret) { Assert.notNull(clientSecret, "clientSecret cannot be null"); this.clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8); return this; } /** - * Creates a {@code RestClientSpringOpaqueTokenIntrospector} - * @return the {@link RestClientSpringOpaqueTokenIntrospector} - * @since 7.1 + * Adds a {@link Consumer} to customize the + * {@link RestClientOpaqueTokenIntrospector} after it is built. This allows for + * additional configuration that cannot be expressed through the builder methods. + * @param postProcessor the {@link Consumer} to customize the introspector + * @return the {@link RestClientOpaqueTokenIntrospector.Builder} */ - public RestClientSpringOpaqueTokenIntrospector build() { + public Builder postProcessor(Consumer postProcessor) { + Assert.notNull(postProcessor, "postProcessor cannot be null"); + this.postProcessors.add(postProcessor); + return this; + } + + /** + * Creates a {@code RestClientOpaqueTokenIntrospector} + * @return the {@link RestClientOpaqueTokenIntrospector} + */ + public RestClientOpaqueTokenIntrospector build() { RestClient restClient = RestClient.builder() .defaultHeaders((headers) -> headers.setBasicAuth(this.clientId, this.clientSecret)) .build(); - return new RestClientSpringOpaqueTokenIntrospector(this.introspectionUri, restClient); + RestClientOpaqueTokenIntrospector introspector = new RestClientOpaqueTokenIntrospector( + this.introspectionUri, restClient); + this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector)); + return introspector; } } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/RestClientSpringOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/RestClientOpaqueTokenIntrospectorTests.java similarity index 91% rename from oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/RestClientSpringOpaqueTokenIntrospectorTests.java rename to oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/RestClientOpaqueTokenIntrospectorTests.java index 8b3fefdaeb..2ba3d3e0f0 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/RestClientSpringOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/RestClientOpaqueTokenIntrospectorTests.java @@ -45,11 +45,11 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.mock; /** - * Tests for {@link RestClientSpringOpaqueTokenIntrospector} + * Tests for {@link RestClientOpaqueTokenIntrospector} * * @author Andrey Litvitski */ -public class RestClientSpringOpaqueTokenIntrospectorTests { +public class RestClientOpaqueTokenIntrospectorTests { private static final String INTROSPECTION_URL = "https://server.example.com"; @@ -112,7 +112,7 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { try (MockWebServer server = new MockWebServer()) { server.setDispatcher(requiresAuth(CLIENT_ID, CLIENT_SECRET, ACTIVE_RESPONSE)); String introspectUri = server.url("/introspect").toString(); - OpaqueTokenIntrospector introspectionClient = RestClientSpringOpaqueTokenIntrospector + OpaqueTokenIntrospector introspectionClient = RestClientOpaqueTokenIntrospector .withIntrospectionUri(introspectUri) .clientId(CLIENT_ID) .clientSecret(CLIENT_SECRET) @@ -140,7 +140,7 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { try (MockWebServer server = new MockWebServer()) { server.setDispatcher(requiresAuth(CLIENT_ID, CLIENT_SECRET, ACTIVE_RESPONSE)); String introspectUri = server.url("/introspect").toString(); - OpaqueTokenIntrospector introspectionClient = RestClientSpringOpaqueTokenIntrospector + OpaqueTokenIntrospector introspectionClient = RestClientOpaqueTokenIntrospector .withIntrospectionUri(introspectUri) .clientId(CLIENT_ID) .clientSecret("wrong") @@ -155,7 +155,7 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { try (MockWebServer server = new MockWebServer()) { server.setDispatcher(requiresAuth(CLIENT_ID, CLIENT_SECRET, INACTIVE_RESPONSE)); String introspectUri = server.url("/introspect").toString(); - OpaqueTokenIntrospector introspectionClient = RestClientSpringOpaqueTokenIntrospector + OpaqueTokenIntrospector introspectionClient = RestClientOpaqueTokenIntrospector .withIntrospectionUri(introspectUri) .clientId(CLIENT_ID) .clientSecret(CLIENT_SECRET) @@ -179,7 +179,7 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { """; server.setDispatcher(requiresAuth(CLIENT_ID, CLIENT_SECRET, response)); String introspectUri = server.url("/introspect").toString(); - OpaqueTokenIntrospector introspectionClient = RestClientSpringOpaqueTokenIntrospector + OpaqueTokenIntrospector introspectionClient = RestClientOpaqueTokenIntrospector .withIntrospectionUri(introspectUri) .clientId(CLIENT_ID) .clientSecret(CLIENT_SECRET) @@ -201,7 +201,7 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { server.start(); String introspectUri = server.url("/introspect").toString(); server.shutdown(); - OpaqueTokenIntrospector introspectionClient = RestClientSpringOpaqueTokenIntrospector + OpaqueTokenIntrospector introspectionClient = RestClientOpaqueTokenIntrospector .withIntrospectionUri(introspectUri) .clientId(CLIENT_ID) .clientSecret(CLIENT_SECRET) @@ -216,7 +216,7 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { try (MockWebServer server = new MockWebServer()) { server.setDispatcher(requiresAuth(CLIENT_ID, CLIENT_SECRET, "{}")); String introspectUri = server.url("/introspect").toString(); - OpaqueTokenIntrospector introspectionClient = RestClientSpringOpaqueTokenIntrospector + OpaqueTokenIntrospector introspectionClient = RestClientOpaqueTokenIntrospector .withIntrospectionUri(introspectUri) .clientId(CLIENT_ID) .clientSecret(CLIENT_SECRET) @@ -231,7 +231,7 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { try (MockWebServer server = new MockWebServer()) { server.setDispatcher(requiresAuth(CLIENT_ID, CLIENT_SECRET, INVALID_RESPONSE)); String introspectUri = server.url("/introspect").toString(); - OpaqueTokenIntrospector introspectionClient = RestClientSpringOpaqueTokenIntrospector + OpaqueTokenIntrospector introspectionClient = RestClientOpaqueTokenIntrospector .withIntrospectionUri(introspectUri) .clientId(CLIENT_ID) .clientSecret(CLIENT_SECRET) @@ -247,7 +247,7 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { try (MockWebServer server = new MockWebServer()) { server.setDispatcher(requiresAuth(CLIENT_ID, CLIENT_SECRET, MALFORMED_SCOPE_RESPONSE)); String introspectUri = server.url("/introspect").toString(); - OpaqueTokenIntrospector introspectionClient = RestClientSpringOpaqueTokenIntrospector + OpaqueTokenIntrospector introspectionClient = RestClientOpaqueTokenIntrospector .withIntrospectionUri(introspectUri) .clientId(CLIENT_ID) .clientSecret(CLIENT_SECRET) @@ -265,7 +265,7 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { try (MockWebServer server = new MockWebServer()) { server.setDispatcher(requiresAuth(CLIENT_ID, CLIENT_SECRET, ACTIVE_RESPONSE)); String introspectUri = server.url("/introspect").toString(); - OpaqueTokenIntrospector introspectionClient = RestClientSpringOpaqueTokenIntrospector + OpaqueTokenIntrospector introspectionClient = RestClientOpaqueTokenIntrospector .withIntrospectionUri(introspectUri) .clientId(CLIENT_ID) .clientSecret(CLIENT_SECRET) @@ -282,8 +282,8 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { @Test public void setRequestEntityConverterWhenConverterIsNullThenExceptionIsThrown() { RestClient restClient = mock(RestClient.class); - RestClientSpringOpaqueTokenIntrospector introspectionClient = new RestClientSpringOpaqueTokenIntrospector( - INTROSPECTION_URL, restClient); + RestClientOpaqueTokenIntrospector introspectionClient = new RestClientOpaqueTokenIntrospector(INTROSPECTION_URL, + restClient); assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> introspectionClient.setRequestEntityConverter(null)); } @@ -291,8 +291,8 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { @Test public void setAuthenticationConverterWhenConverterIsNullThenExceptionIsThrown() { RestClient restClient = mock(RestClient.class); - RestClientSpringOpaqueTokenIntrospector introspectionClient = new RestClientSpringOpaqueTokenIntrospector( - INTROSPECTION_URL, restClient); + RestClientOpaqueTokenIntrospector introspectionClient = new RestClientOpaqueTokenIntrospector(INTROSPECTION_URL, + restClient); assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> introspectionClient.setAuthenticationConverter(null)); } @@ -311,7 +311,7 @@ public class RestClientSpringOpaqueTokenIntrospectorTests { RestClient restClient = RestClient.builder() .defaultHeaders((h) -> h.setBasicAuth("client%&1", "secret@$2")) .build(); - OpaqueTokenIntrospector introspectionClient = new RestClientSpringOpaqueTokenIntrospector(introspectUri, + OpaqueTokenIntrospector introspectionClient = new RestClientOpaqueTokenIntrospector(introspectUri, restClient); assertThatExceptionOfType(OAuth2IntrospectionException.class) .isThrownBy(() -> introspectionClient.introspect("token"));