From 6be189044e51523642b6a34962062abf0703da87 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 28 Jun 2022 14:53:51 +0200 Subject: [PATCH] Add support for the Spring Security OAuth2 native support; fixes gh-2182 --- pom.xml | 1 + spring-cloud-sleuth-autoconfigure/pom.xml | 6 ++ .../TraceWebClientAutoConfiguration.java | 25 +++++++ spring-cloud-sleuth-instrumentation/pom.xml | 5 ++ ...aceDefaultOAuth2UserServiceCustomizer.java | 72 +++++++++++++++++++ ...faultOAuth2UserServiceCustomizerTests.java | 56 +++++++++++++++ 6 files changed, 165 insertions(+) create mode 100644 spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceDefaultOAuth2UserServiceCustomizer.java create mode 100644 spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceDefaultOAuth2UserServiceCustomizerTests.java diff --git a/pom.xml b/pom.xml index 0bcf654b3..c05e566c5 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,7 @@ 1.7.1 3.3.0 3.0.1 + 2.2.0.RELEASE diff --git a/spring-cloud-sleuth-autoconfigure/pom.xml b/spring-cloud-sleuth-autoconfigure/pom.xml index dede89845..d0646bbba 100644 --- a/spring-cloud-sleuth-autoconfigure/pom.xml +++ b/spring-cloud-sleuth-autoconfigure/pom.xml @@ -153,6 +153,12 @@ spring-security-oauth2-autoconfigure true + + org.springframework.security + spring-security-oauth2-client + true + + diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientAutoConfiguration.java index 3e7d85c46..17e740773 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -18,9 +18,11 @@ package org.springframework.cloud.sleuth.autoconfig.instrument.web.client; import reactor.netty.http.client.HttpClient; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -38,6 +40,7 @@ import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration; import org.springframework.cloud.sleuth.http.HttpClientHandler; import org.springframework.cloud.sleuth.instrument.web.client.HttpClientBeanPostProcessor; import org.springframework.cloud.sleuth.instrument.web.client.LazyTraceClientHttpRequestInterceptor; +import org.springframework.cloud.sleuth.instrument.web.client.TraceDefaultOAuth2UserServiceCustomizer; import org.springframework.cloud.sleuth.instrument.web.client.TraceRequestHttpHeadersFilter; import org.springframework.cloud.sleuth.instrument.web.client.TraceResponseHttpHeadersFilter; import org.springframework.cloud.sleuth.instrument.web.client.TraceRestTemplateBeanPostProcessor; @@ -52,6 +55,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; @@ -148,6 +152,7 @@ class TraceWebClientAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ UserInfoRestTemplateCustomizer.class, OAuth2RestTemplate.class }) + @Deprecated // Use Spring-Security OAuth2 support protected static class TraceOAuthConfiguration { @Bean @@ -164,4 +169,24 @@ class TraceWebClientAutoConfiguration { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(DefaultOAuth2UserService.class) + protected static class TraceSpringSecurityOAuth2Configuration { + + @Bean + static BeanPostProcessor traceDefaultOAuth2UserServiceBeanPostProcessor(BeanFactory beanFactory) { + return new BeanPostProcessor() { + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof DefaultOAuth2UserService) { + new TraceDefaultOAuth2UserServiceCustomizer(beanFactory) + .customize((DefaultOAuth2UserService) bean); + } + return bean; + } + }; + } + + } + } diff --git a/spring-cloud-sleuth-instrumentation/pom.xml b/spring-cloud-sleuth-instrumentation/pom.xml index 9ef5c2232..789dc8c0a 100644 --- a/spring-cloud-sleuth-instrumentation/pom.xml +++ b/spring-cloud-sleuth-instrumentation/pom.xml @@ -152,6 +152,11 @@ spring-security-oauth2-autoconfigure true + + org.springframework.security + spring-security-oauth2-client + true + org.springframework.boot diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceDefaultOAuth2UserServiceCustomizer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceDefaultOAuth2UserServiceCustomizer.java new file mode 100644 index 000000000..336dbbd06 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceDefaultOAuth2UserServiceCustomizer.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2021 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.cloud.sleuth.instrument.web.client; + +import java.lang.reflect.Field; +import java.util.Objects; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.instrument.web.mvc.TracingClientHttpRequestInterceptor; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +/** + * Customizes a {@link DefaultOAuth2UserService} by providing it with a trace interceptor. + * + * @author Marcin Grzejszczak + * @since 3.0.6 + */ +public class TraceDefaultOAuth2UserServiceCustomizer { + + private static final Log log = LogFactory.getLog(TraceDefaultOAuth2UserServiceCustomizer.class); + + private final BeanFactory beanFactory; + + private static final Field REST_OPERATIONS = ReflectionUtils.findField(DefaultOAuth2UserService.class, + "restOperations", RestOperations.class); + + public TraceDefaultOAuth2UserServiceCustomizer(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + public void customize(DefaultOAuth2UserService service) { + try { + ReflectionUtils.makeAccessible(Objects.requireNonNull(REST_OPERATIONS, + "restOperations field was not found in [DefaultOAuth2UserService] class")); + RestOperations restOperations = (RestOperations) REST_OPERATIONS.get(service); + if (!(restOperations instanceof RestTemplate)) { + log.warn( + "Won't instrument the restOperations field in [DefaultOAuth2UserService] class because it's not a RestTemplate object"); + return; + } + RestTemplate template = (RestTemplate) restOperations; + final TracingClientHttpRequestInterceptor interceptor = this.beanFactory + .getBean(TracingClientHttpRequestInterceptor.class); + new RestTemplateInterceptorInjector(interceptor).inject(template); + } + catch (Exception e) { + log.warn("Can't access the restOperations field - won't instrument the [DefaultOAuth2UserService] class", + e); + } + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceDefaultOAuth2UserServiceCustomizerTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceDefaultOAuth2UserServiceCustomizerTests.java new file mode 100644 index 000000000..e5b020bdc --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceDefaultOAuth2UserServiceCustomizerTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013-2021 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.cloud.sleuth.instrument.web.client; + +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.support.StaticListableBeanFactory; +import org.springframework.cloud.sleuth.instrument.web.mvc.TracingClientHttpRequestInterceptor; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.client.RestTemplate; + +class TraceDefaultOAuth2UserServiceCustomizerTests { + + @Test + void should_add_a_trace_interceptor_to_defaultoauth2userservice() { + DefaultOAuth2UserService service = new DefaultOAuth2UserService(); + BeanFactory beanFactory = beanFactory(); + TraceDefaultOAuth2UserServiceCustomizer serviceCustomizer = new TraceDefaultOAuth2UserServiceCustomizer( + beanFactory); + + serviceCustomizer.customize(service); + serviceCustomizer.customize(service); + + Object operations = ReflectionUtils + .getField(ReflectionUtils.findField(DefaultOAuth2UserService.class, "restOperations"), service); + BDDAssertions.then(operations).isInstanceOf(RestTemplate.class); + RestTemplate restTemplate = (RestTemplate) operations; + BDDAssertions.then(restTemplate.getInterceptors()).hasSize(1) + .hasOnlyElementsOfType(TracingClientHttpRequestInterceptor.class); + } + + private BeanFactory beanFactory() { + StaticListableBeanFactory beanFactory = new StaticListableBeanFactory(); + beanFactory.addBean("TracingClientHttpRequestInterceptor", + TracingClientHttpRequestInterceptor.create(null, null)); + return beanFactory; + } + +}