From d528be38bb51993624a22ab7da27dc690d8dba6d Mon Sep 17 00:00:00 2001 From: itsmevichu Date: Sat, 31 Jan 2026 14:15:05 +0530 Subject: [PATCH] Add postProcessor to Introspector Builder Closes gh-18623 Signed-off-by: itsmevichu --- .../SpringOpaqueTokenIntrospector.java | 22 +++++++++++++++++- ...SpringReactiveOpaqueTokenIntrospector.java | 23 ++++++++++++++++++- .../SpringOpaqueTokenIntrospectorTests.java | 19 +++++++++++++++ ...gReactiveOpaqueTokenIntrospectorTests.java | 20 ++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java index 2cd1a8fb34..e5fc91f9e3 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.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; @@ -325,6 +326,8 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private String clientSecret; + private final List> postProcessors = new ArrayList<>(); + private Builder(String introspectionUri) { this.introspectionUri = introspectionUri; } @@ -355,6 +358,20 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector { return this; } + /** + * Adds a {@link Consumer} to customize the {@link SpringOpaqueTokenIntrospector} + * after it is built. This allows for additional configuration that cannot be + * expressed through the builder methods. + * @param customizer the {@link Consumer} to customize the introspector + * @return the {@link SpringOpaqueTokenIntrospector.Builder} + * @since 7.x.x + */ + public Builder postProcessor(Consumer customizer) { + Assert.notNull(customizer, "customizer cannot be null"); + this.postProcessors.add(customizer); + return this; + } + /** * Creates a {@code SpringOpaqueTokenIntrospector} * @return the {@link SpringOpaqueTokenIntrospector} @@ -363,7 +380,10 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector { public SpringOpaqueTokenIntrospector build() { RestTemplate restTemplate = new RestTemplate(); restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(this.clientId, this.clientSecret)); - return new SpringOpaqueTokenIntrospector(this.introspectionUri, restTemplate); + SpringOpaqueTokenIntrospector introspector = new SpringOpaqueTokenIntrospector(this.introspectionUri, + restTemplate); + this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector)); + return introspector; } } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java index bee71d57b7..ff2ab2b21a 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.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 reactor.core.publisher.Mono; @@ -278,6 +279,8 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke private String clientSecret; + private final List> postProcessors = new ArrayList<>(); + private Builder(String introspectionUri) { this.introspectionUri = introspectionUri; } @@ -308,6 +311,21 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke return this; } + /** + * Adds a {@link Consumer} to customize the + * {@link SpringReactiveOpaqueTokenIntrospector} after it is built. This allows + * for additional configuration that cannot be expressed through the builder + * methods. + * @param customizer the {@link Consumer} to customize the introspector + * @return the {@link SpringReactiveOpaqueTokenIntrospector.Builder} + * @since 7.x.x + */ + public Builder postProcessor(Consumer customizer) { + Assert.notNull(customizer, "customizer cannot be null"); + this.postProcessors.add(customizer); + return this; + } + /** * Creates a {@code SpringReactiveOpaqueTokenIntrospector} * @return the {@link SpringReactiveOpaqueTokenIntrospector} @@ -317,7 +335,10 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke WebClient webClient = WebClient.builder() .defaultHeaders((h) -> h.setBasicAuth(this.clientId, this.clientSecret)) .build(); - return new SpringReactiveOpaqueTokenIntrospector(this.introspectionUri, webClient); + SpringReactiveOpaqueTokenIntrospector introspector = new SpringReactiveOpaqueTokenIntrospector( + this.introspectionUri, webClient); + 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/SpringOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java index b75358ded7..8ecca0c9dd 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java @@ -383,6 +383,25 @@ public class SpringOpaqueTokenIntrospectorTests { } } + @Test + public void builderWhenPostProcessorSetThenApplied() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.setDispatcher(requiresAuth(CLIENT_ID, CLIENT_SECRET, ACTIVE_RESPONSE)); + String introspectUri = server.url("/introspect").toString(); + Converter authenticationConverter = mock( + Converter.class); + OAuth2AuthenticatedPrincipal principal = mock(OAuth2AuthenticatedPrincipal.class); + given(authenticationConverter.convert(any())).willReturn(principal); + OpaqueTokenIntrospector introspector = SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectUri) + .clientId(CLIENT_ID) + .clientSecret(CLIENT_SECRET) + .postProcessor((i) -> i.setAuthenticationConverter(authenticationConverter)) + .build(); + OAuth2AuthenticatedPrincipal result = introspector.introspect("token"); + assertThat(result).isSameAs(principal); + } + } + private static ResponseEntity> response(String content) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java index 5d9b2f7223..95fe223ef0 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java @@ -308,6 +308,26 @@ public class SpringReactiveOpaqueTokenIntrospectorTests { } } + @Test + public void builderWhenPostProcessorSetThenApplied() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.setDispatcher(requiresAuth(CLIENT_ID, CLIENT_SECRET, ACTIVE_RESPONSE)); + String introspectUri = server.url("/introspect").toString(); + Converter> authenticationConverter = mock( + Converter.class); + OAuth2AuthenticatedPrincipal principal = mock(OAuth2AuthenticatedPrincipal.class); + given(authenticationConverter.convert(any())).willReturn((Mono) Mono.just(principal)); + ReactiveOpaqueTokenIntrospector introspector = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri(introspectUri) + .clientId(CLIENT_ID) + .clientSecret(CLIENT_SECRET) + .postProcessor((i) -> i.setAuthenticationConverter(authenticationConverter)) + .build(); + OAuth2AuthenticatedPrincipal result = introspector.introspect("token").block(); + assertThat(result).isSameAs(principal); + } + } + private WebClient mockResponse(String response) { return mockResponse(toMap(response)); }