From 636587188fd3c6b41bfe5b79f7b35a44fd665cb8 Mon Sep 17 00:00:00 2001 From: itsmevichu Date: Sun, 1 Feb 2026 16:07:53 +0530 Subject: [PATCH 1/2] Add builder customizer for SpringOpaqueTokenIntrospector and SpringReactiveOpaqueTokenIntrospector Signed-off-by: itsmevichu --- ...esourceServerOpaqueTokenConfiguration.java | 13 ++++--- ...queTokenIntrospectorBuilderCustomizer.java | 39 +++++++++++++++++++ ...esourceServerOpaqueTokenConfiguration.java | 13 ++++--- ...queTokenIntrospectorBuilderCustomizer.java | 39 +++++++++++++++++++ ...2ResourceServerAutoConfigurationTests.java | 24 ++++++++++++ 5 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer.java create mode 100644 module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/SpringOpaqueTokenIntrospectorBuilderCustomizer.java diff --git a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java index d97b249c4cf6..a3799168d4c8 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java +++ b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.boot.security.oauth2.server.resource.autoconfigure.reactive; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -45,16 +46,18 @@ static class OpaqueTokenIntrospectionClientConfiguration { @Bean @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.opaquetoken.introspection-uri") - SpringReactiveOpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2ResourceServerProperties properties) { + SpringReactiveOpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2ResourceServerProperties properties, + ObjectProvider customizers) { OAuth2ResourceServerProperties.Opaquetoken opaquetoken = properties.getOpaquetoken(); String clientId = opaquetoken.getClientId(); Assert.state(clientId != null, "'clientId' must not be null"); String clientSecret = opaquetoken.getClientSecret(); Assert.state(clientSecret != null, "'clientSecret' must not be null"); - return SpringReactiveOpaqueTokenIntrospector.withIntrospectionUri(opaquetoken.getIntrospectionUri()) - .clientId(clientId) - .clientSecret(clientSecret) - .build(); + SpringReactiveOpaqueTokenIntrospector.Builder builder = SpringReactiveOpaqueTokenIntrospector.withIntrospectionUri(opaquetoken.getIntrospectionUri()) + .clientId(clientId) + .clientSecret(clientSecret); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder.build(); } } diff --git a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer.java b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer.java new file mode 100644 index 000000000000..d09fc335d692 --- /dev/null +++ b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-present 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.boot.security.oauth2.server.resource.autoconfigure.reactive; + +import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; +import org.springframework.security.oauth2.server.resource.introspection.SpringReactiveOpaqueTokenIntrospector; + +/** + * Callback interface for the customization of the + * {@link SpringReactiveOpaqueTokenIntrospector.Builder} used to create the + * auto-configured {@link ReactiveOpaqueTokenIntrospector}. + * + * @author Vishnutheep B + * @since 4.x.x + */ +@FunctionalInterface +public interface SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer { + + /** + * Customize the given {@code builder}. + * @param builder the {@code builder} to customize + */ + void customize(SpringReactiveOpaqueTokenIntrospector.Builder builder); + +} diff --git a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerOpaqueTokenConfiguration.java b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerOpaqueTokenConfiguration.java index c9852e8c9f66..6c7720241600 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerOpaqueTokenConfiguration.java +++ b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerOpaqueTokenConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.boot.security.oauth2.server.resource.autoconfigure.servlet; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -47,7 +48,8 @@ static class OpaqueTokenIntrospectionClientConfiguration { @Bean @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.opaquetoken.introspection-uri") - SpringOpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2ResourceServerProperties properties) { + SpringOpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2ResourceServerProperties properties, + ObjectProvider customizers) { OAuth2ResourceServerProperties.Opaquetoken opaquetoken = properties.getOpaquetoken(); String introspectionUri = opaquetoken.getIntrospectionUri(); Assert.state(introspectionUri != null, "'introspectionUri' must not be null"); @@ -55,10 +57,11 @@ SpringOpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2ResourceServerProper Assert.state(clientId != null, "'clientId' must not be null"); String clientSecret = opaquetoken.getClientSecret(); Assert.state(clientSecret != null, "'clientSecret' must not be null"); - return SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) - .clientId(clientId) - .clientSecret(clientSecret) - .build(); + SpringOpaqueTokenIntrospector.Builder builder = SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) + .clientId(clientId) + .clientSecret(clientSecret); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder.build(); } } diff --git a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/SpringOpaqueTokenIntrospectorBuilderCustomizer.java b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/SpringOpaqueTokenIntrospectorBuilderCustomizer.java new file mode 100644 index 000000000000..0f613dfc2c23 --- /dev/null +++ b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/SpringOpaqueTokenIntrospectorBuilderCustomizer.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-present 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.boot.security.oauth2.server.resource.autoconfigure.servlet; + +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; +import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector; + +/** + * Callback interface for the customization of the + * {@link SpringOpaqueTokenIntrospector.Builder} used to create the auto-configured + * {@link OpaqueTokenIntrospector}. + * + * @author Vishnutheep B + * @since 4.x.x + */ +@FunctionalInterface +public interface SpringOpaqueTokenIntrospectorBuilderCustomizer { + + /** + * Customize the given {@code builder}. + * @param builder the {@code builder} to customize + */ + void customize(SpringOpaqueTokenIntrospector.Builder builder); + +} diff --git a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java index 31beee478411..5b196de769e4 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java +++ b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java @@ -465,9 +465,21 @@ void autoConfigurationWhenIntrospectionUriAvailableShouldConfigureIntrospectionC .run((context) -> { assertThat(context).hasSingleBean(OpaqueTokenIntrospector.class); assertThat(getBearerTokenFilter(context)).isNotNull(); + assertSpringOpaqueTokenIntrospectorBuilderCustomization(context); }); } + private void assertSpringOpaqueTokenIntrospectorBuilderCustomization(AssertableWebApplicationContext context) { + SpringOpaqueTokenIntrospectorBuilderCustomizer customizer = context.getBean( + "opaqueTokenIntrospectorBuilderCustomizer", SpringOpaqueTokenIntrospectorBuilderCustomizer.class); + SpringOpaqueTokenIntrospectorBuilderCustomizer anotherCustomizer = context.getBean( + "anotherOpaqueTokenIntrospectorBuilderCustomizer", + SpringOpaqueTokenIntrospectorBuilderCustomizer.class); + InOrder inOrder = inOrder(customizer, anotherCustomizer); + inOrder.verify(customizer).customize(any()); + inOrder.verify(anotherCustomizer).customize(any()); + } + @Test void opaqueTokenIntrospectorIsConditionalOnMissingBean() { this.contextRunner @@ -911,6 +923,18 @@ JwkSetUriJwtDecoderBuilderCustomizer decoderBuilderCustomizer() { JwkSetUriJwtDecoderBuilderCustomizer anotherDecoderBuilderCustomizer() { return mock(JwkSetUriJwtDecoderBuilderCustomizer.class); } + + @Bean + @Order(1) + SpringOpaqueTokenIntrospectorBuilderCustomizer opaqueTokenIntrospectorBuilderCustomizer() { + return mock(SpringOpaqueTokenIntrospectorBuilderCustomizer.class); + } + + @Bean + @Order(2) + SpringOpaqueTokenIntrospectorBuilderCustomizer anotherOpaqueTokenIntrospectorBuilderCustomizer() { + return mock(SpringOpaqueTokenIntrospectorBuilderCustomizer.class); + } } From de0295344fea5f5a4628431b4ae47ce227683e6c Mon Sep 17 00:00:00 2001 From: itsmevichu Date: Thu, 12 Feb 2026 09:53:27 +0530 Subject: [PATCH 2/2] Add unit test for SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer Signed-off-by: itsmevichu --- ...esourceServerOpaqueTokenConfiguration.java | 7 ++--- ...esourceServerOpaqueTokenConfiguration.java | 7 ++--- ...2ResourceServerAutoConfigurationTests.java | 26 +++++++++++++++++++ ...2ResourceServerAutoConfigurationTests.java | 8 +++--- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java index a3799168d4c8..53afbf093863 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java +++ b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java @@ -53,9 +53,10 @@ SpringReactiveOpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2ResourceServ Assert.state(clientId != null, "'clientId' must not be null"); String clientSecret = opaquetoken.getClientSecret(); Assert.state(clientSecret != null, "'clientSecret' must not be null"); - SpringReactiveOpaqueTokenIntrospector.Builder builder = SpringReactiveOpaqueTokenIntrospector.withIntrospectionUri(opaquetoken.getIntrospectionUri()) - .clientId(clientId) - .clientSecret(clientSecret); + SpringReactiveOpaqueTokenIntrospector.Builder builder = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri(opaquetoken.getIntrospectionUri()) + .clientId(clientId) + .clientSecret(clientSecret); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); } diff --git a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerOpaqueTokenConfiguration.java b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerOpaqueTokenConfiguration.java index 6c7720241600..a0fbeaa88f51 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerOpaqueTokenConfiguration.java +++ b/module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerOpaqueTokenConfiguration.java @@ -57,9 +57,10 @@ SpringOpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2ResourceServerProper Assert.state(clientId != null, "'clientId' must not be null"); String clientSecret = opaquetoken.getClientSecret(); Assert.state(clientSecret != null, "'clientSecret' must not be null"); - SpringOpaqueTokenIntrospector.Builder builder = SpringOpaqueTokenIntrospector.withIntrospectionUri(introspectionUri) - .clientId(clientId) - .clientSecret(clientSecret); + SpringOpaqueTokenIntrospector.Builder builder = SpringOpaqueTokenIntrospector + .withIntrospectionUri(introspectionUri) + .clientId(clientId) + .clientSecret(clientSecret); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); } diff --git a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java index 824656ae063b..b2b2a8996538 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java +++ b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java @@ -414,9 +414,23 @@ void autoConfigurationWhenIntrospectionUriAvailableShouldConfigureIntrospectionC .run((context) -> { assertThat(context).hasSingleBean(ReactiveOpaqueTokenIntrospector.class); assertFilterConfiguredWithOpaqueTokenAuthenticationManager(context); + assertSpringReactiveOpaqueTokenIntrospectorBuilderCustomization(context); }); } + private void assertSpringReactiveOpaqueTokenIntrospectorBuilderCustomization( + AssertableReactiveWebApplicationContext context) { + SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer customizer = context.getBean( + "reactiveOpaqueTokenIntrospectorBuilderCustomizer", + SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer.class); + SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer anotherCustomizer = context.getBean( + "anotherReactiveOpaqueTokenIntrospectorBuilderCustomizer", + SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer.class); + InOrder inOrder = inOrder(customizer, anotherCustomizer); + inOrder.verify(customizer).customize(any()); + inOrder.verify(anotherCustomizer).customize(any()); + } + @Test void autoConfigurationWhenJwkSetUriAndIntrospectionUriAvailable() { this.contextRunner @@ -929,6 +943,18 @@ JwkSetUriReactiveJwtDecoderBuilderCustomizer anotherDecoderBuilderCustomizer() { return mock(JwkSetUriReactiveJwtDecoderBuilderCustomizer.class); } + @Bean + @Order(1) + SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer reactiveOpaqueTokenIntrospectorBuilderCustomizer() { + return mock(SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer.class); + } + + @Bean + @Order(2) + SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer anotherReactiveOpaqueTokenIntrospectorBuilderCustomizer() { + return mock(SpringReactiveOpaqueTokenIntrospectorBuilderCustomizer.class); + } + } @Configuration(proxyBeanMethods = false) diff --git a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java index 5b196de769e4..c356e83174cf 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java +++ b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java @@ -470,8 +470,8 @@ void autoConfigurationWhenIntrospectionUriAvailableShouldConfigureIntrospectionC } private void assertSpringOpaqueTokenIntrospectorBuilderCustomization(AssertableWebApplicationContext context) { - SpringOpaqueTokenIntrospectorBuilderCustomizer customizer = context.getBean( - "opaqueTokenIntrospectorBuilderCustomizer", SpringOpaqueTokenIntrospectorBuilderCustomizer.class); + SpringOpaqueTokenIntrospectorBuilderCustomizer customizer = context + .getBean("opaqueTokenIntrospectorBuilderCustomizer", SpringOpaqueTokenIntrospectorBuilderCustomizer.class); SpringOpaqueTokenIntrospectorBuilderCustomizer anotherCustomizer = context.getBean( "anotherOpaqueTokenIntrospectorBuilderCustomizer", SpringOpaqueTokenIntrospectorBuilderCustomizer.class); @@ -923,13 +923,13 @@ JwkSetUriJwtDecoderBuilderCustomizer decoderBuilderCustomizer() { JwkSetUriJwtDecoderBuilderCustomizer anotherDecoderBuilderCustomizer() { return mock(JwkSetUriJwtDecoderBuilderCustomizer.class); } - + @Bean @Order(1) SpringOpaqueTokenIntrospectorBuilderCustomizer opaqueTokenIntrospectorBuilderCustomizer() { return mock(SpringOpaqueTokenIntrospectorBuilderCustomizer.class); } - + @Bean @Order(2) SpringOpaqueTokenIntrospectorBuilderCustomizer anotherOpaqueTokenIntrospectorBuilderCustomizer() {