Add postProcessor to Introspector Builder

Closes gh-18623

Signed-off-by: itsmevichu <vishnutheep@gmail.com>
This commit is contained in:
itsmevichu 2026-01-31 14:15:05 +05:30 committed by Josh Cummings
parent da0cd0bc68
commit d528be38bb
4 changed files with 82 additions and 2 deletions

View File

@ -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<Consumer<SpringOpaqueTokenIntrospector>> 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<SpringOpaqueTokenIntrospector> 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;
}
}

View File

@ -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<Consumer<SpringReactiveOpaqueTokenIntrospector>> 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<SpringReactiveOpaqueTokenIntrospector> 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;
}
}

View File

@ -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<OAuth2TokenIntrospectionClaimAccessor, OAuth2AuthenticatedPrincipal> 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<Map<String, Object>> response(String content) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

View File

@ -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<OAuth2TokenIntrospectionClaimAccessor, Mono<? extends OAuth2AuthenticatedPrincipal>> 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));
}