Add support for the Spring Security OAuth2 native support; fixes gh-2182

This commit is contained in:
Marcin Grzejszczak
2022-06-28 14:53:51 +02:00
parent 747ff21237
commit 6be189044e
6 changed files with 165 additions and 0 deletions

View File

@@ -80,6 +80,7 @@
<javax.resource-api.version>1.7.1</javax.resource-api.version>
<cglib-nodep.version>3.3.0</cglib-nodep.version>
<objenesis.version>3.0.1</objenesis.version>
<!-- Deprecated - reached EOL -->
<spring-security-oauth2.version>2.2.0.RELEASE</spring-security-oauth2.version>
<!-- Until we switch it to true in sc-build -->

View File

@@ -153,6 +153,12 @@
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<optional>true</optional>
</dependency>
<!-- BRAVE -->
<dependency>

View File

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

View File

@@ -152,6 +152,11 @@
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -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);
}
}
}

View File

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