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..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 @@ -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,19 @@ 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()) + SpringReactiveOpaqueTokenIntrospector.Builder builder = SpringReactiveOpaqueTokenIntrospector + .withIntrospectionUri(opaquetoken.getIntrospectionUri()) .clientId(clientId) - .clientSecret(clientSecret) - .build(); + .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..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 @@ -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,12 @@ 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) + SpringOpaqueTokenIntrospector.Builder builder = SpringOpaqueTokenIntrospector + .withIntrospectionUri(introspectionUri) .clientId(clientId) - .clientSecret(clientSecret) - .build(); + .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/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 31beee478411..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 @@ -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 @@ -912,6 +924,18 @@ JwkSetUriJwtDecoderBuilderCustomizer anotherDecoderBuilderCustomizer() { return mock(JwkSetUriJwtDecoderBuilderCustomizer.class); } + @Bean + @Order(1) + SpringOpaqueTokenIntrospectorBuilderCustomizer opaqueTokenIntrospectorBuilderCustomizer() { + return mock(SpringOpaqueTokenIntrospectorBuilderCustomizer.class); + } + + @Bean + @Order(2) + SpringOpaqueTokenIntrospectorBuilderCustomizer anotherOpaqueTokenIntrospectorBuilderCustomizer() { + return mock(SpringOpaqueTokenIntrospectorBuilderCustomizer.class); + } + } @Configuration(proxyBeanMethods = false)