Skip to content

Commit 2a5bd6e

Browse files
committed
Align Servlet ExchangeFilterFunction CoreSubscriber
Fixes gh-7422
1 parent d17cbe4 commit 2a5bd6e

File tree

6 files changed

+426
-301
lines changed

6 files changed

+426
-301
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ImportSelector.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,37 +15,49 @@
1515
*/
1616
package org.springframework.security.config.annotation.web.configuration;
1717

18-
import java.util.ArrayList;
19-
import java.util.List;
20-
2118
import org.springframework.context.annotation.ImportSelector;
2219
import org.springframework.core.type.AnnotationMetadata;
2320
import org.springframework.util.ClassUtils;
2421

22+
import java.util.ArrayList;
23+
import java.util.List;
24+
2525
/**
26-
* Used by {@link EnableWebSecurity} to conditionally import {@link OAuth2ClientConfiguration}
27-
* when the {@code spring-security-oauth2-client} module is present on the classpath and
28-
* {@link OAuth2ResourceServerConfiguration} when the {@code spring-security-oauth2-resource-server}
29-
* module is on the classpath.
26+
* Used by {@link EnableWebSecurity} to conditionally import:
27+
*
28+
* <ul>
29+
* <li>{@link OAuth2ClientConfiguration} when the {@code spring-security-oauth2-client} module is present on the classpath</li>
30+
* <li>{@link SecurityReactorContextConfiguration} when the {@code spring-webflux} and {@code spring-security-oauth2-client} module is present on the classpath</li>
31+
* <li>{@link OAuth2ResourceServerConfiguration} when the {@code spring-security-oauth2-resource-server} module is present on the classpath</li>
32+
* </ul>
3033
*
3134
* @author Joe Grandja
3235
* @author Josh Cummings
3336
* @since 5.1
3437
* @see OAuth2ClientConfiguration
38+
* @see SecurityReactorContextConfiguration
39+
* @see OAuth2ResourceServerConfiguration
3540
*/
3641
final class OAuth2ImportSelector implements ImportSelector {
3742

3843
@Override
3944
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
4045
List<String> imports = new ArrayList<>();
4146

42-
if (ClassUtils.isPresent(
43-
"org.springframework.security.oauth2.client.registration.ClientRegistration", getClass().getClassLoader())) {
47+
boolean oauth2ClientPresent = ClassUtils.isPresent(
48+
"org.springframework.security.oauth2.client.registration.ClientRegistration", getClass().getClassLoader());
49+
if (oauth2ClientPresent) {
4450
imports.add("org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration");
4551
}
4652

53+
boolean webfluxPresent = ClassUtils.isPresent(
54+
"org.springframework.web.reactive.function.client.ExchangeFilterFunction", getClass().getClassLoader());
55+
if (webfluxPresent && oauth2ClientPresent) {
56+
imports.add("org.springframework.security.config.annotation.web.configuration.SecurityReactorContextConfiguration");
57+
}
58+
4759
if (ClassUtils.isPresent(
48-
"org.springframework.security.oauth2.server.resource.BearerTokenError", getClass().getClassLoader())) {
60+
"org.springframework.security.oauth2.server.resource.BearerTokenError", getClass().getClassLoader())) {
4961
imports.add("org.springframework.security.config.annotation.web.configuration.OAuth2ResourceServerConfiguration");
5062
}
5163

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.config.annotation.web.configuration;
17+
18+
import org.reactivestreams.Publisher;
19+
import org.reactivestreams.Subscription;
20+
import org.springframework.beans.factory.DisposableBean;
21+
import org.springframework.beans.factory.InitializingBean;
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.Configuration;
24+
import org.springframework.security.core.Authentication;
25+
import org.springframework.security.core.context.SecurityContextHolder;
26+
import org.springframework.util.CollectionUtils;
27+
import org.springframework.web.context.request.RequestContextHolder;
28+
import org.springframework.web.context.request.ServletRequestAttributes;
29+
import reactor.core.CoreSubscriber;
30+
import reactor.core.publisher.Hooks;
31+
import reactor.core.publisher.Operators;
32+
import reactor.util.context.Context;
33+
34+
import javax.servlet.http.HttpServletRequest;
35+
import javax.servlet.http.HttpServletResponse;
36+
import java.util.Collections;
37+
import java.util.HashMap;
38+
import java.util.Map;
39+
import java.util.function.Function;
40+
41+
import static org.springframework.security.config.annotation.web.configuration.SecurityReactorContextConfiguration.SecurityReactorContextSubscriber.SECURITY_CONTEXT_ATTRIBUTES;
42+
43+
/**
44+
* {@link Configuration} that (potentially) adds a "decorating" {@code Publisher}
45+
* for the last operator created in every {@code Mono} or {@code Flux}.
46+
*
47+
* <p>
48+
* The {@code Publisher} is solely responsible for adding
49+
* the current {@code HttpServletRequest}, {@code HttpServletResponse} and {@code Authentication}
50+
* to the Reactor {@code Context} so that it's accessible in every flow, if required.
51+
*
52+
* @author Joe Grandja
53+
* @since 5.2
54+
* @see OAuth2ImportSelector
55+
*/
56+
@Configuration(proxyBeanMethods = false)
57+
class SecurityReactorContextConfiguration {
58+
59+
@Bean
60+
SecurityReactorContextSubscriberRegistrar securityReactorContextSubscriberRegistrar() {
61+
return new SecurityReactorContextSubscriberRegistrar();
62+
}
63+
64+
static class SecurityReactorContextSubscriberRegistrar implements InitializingBean, DisposableBean {
65+
private static final String SECURITY_REACTOR_CONTEXT_OPERATOR_KEY = "org.springframework.security.SECURITY_REACTOR_CONTEXT_OPERATOR";
66+
67+
@Override
68+
public void afterPropertiesSet() throws Exception {
69+
Function<? super Publisher<Object>, ? extends Publisher<Object>> lifter =
70+
Operators.liftPublisher((pub, sub) -> createSubscriberIfNecessary(sub));
71+
72+
Hooks.onLastOperator(SECURITY_REACTOR_CONTEXT_OPERATOR_KEY, pub -> {
73+
if (CollectionUtils.isEmpty(getContextAttributes())) {
74+
// No need to decorate so return original Publisher
75+
return pub;
76+
}
77+
return lifter.apply(pub);
78+
});
79+
}
80+
81+
@Override
82+
public void destroy() throws Exception {
83+
Hooks.resetOnLastOperator(SECURITY_REACTOR_CONTEXT_OPERATOR_KEY);
84+
}
85+
86+
<T> CoreSubscriber<T> createSubscriberIfNecessary(CoreSubscriber<T> delegate) {
87+
if (delegate.currentContext().hasKey(SECURITY_CONTEXT_ATTRIBUTES)) {
88+
// Already enriched. No need to create Subscriber so return original
89+
return delegate;
90+
}
91+
return new SecurityReactorContextSubscriber<>(delegate, getContextAttributes());
92+
}
93+
94+
private static Map<Object, Object> getContextAttributes() {
95+
HttpServletRequest servletRequest = null;
96+
HttpServletResponse servletResponse = null;
97+
ServletRequestAttributes requestAttributes =
98+
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
99+
if (requestAttributes != null) {
100+
servletRequest = requestAttributes.getRequest();
101+
servletResponse = requestAttributes.getResponse();
102+
}
103+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
104+
if (authentication == null && servletRequest == null && servletResponse == null) {
105+
return Collections.emptyMap();
106+
}
107+
108+
Map<Object, Object> contextAttributes = new HashMap<>();
109+
if (servletRequest != null) {
110+
contextAttributes.put(HttpServletRequest.class, servletRequest);
111+
}
112+
if (servletResponse != null) {
113+
contextAttributes.put(HttpServletResponse.class, servletResponse);
114+
}
115+
if (authentication != null) {
116+
contextAttributes.put(Authentication.class, authentication);
117+
}
118+
119+
return contextAttributes;
120+
}
121+
}
122+
123+
static class SecurityReactorContextSubscriber<T> implements CoreSubscriber<T> {
124+
static final String SECURITY_CONTEXT_ATTRIBUTES = "org.springframework.security.SECURITY_CONTEXT_ATTRIBUTES";
125+
private final CoreSubscriber<T> delegate;
126+
private final Context context;
127+
128+
SecurityReactorContextSubscriber(CoreSubscriber<T> delegate, Map<Object, Object> attributes) {
129+
this.delegate = delegate;
130+
Context currentContext = this.delegate.currentContext();
131+
Context context;
132+
if (currentContext.hasKey(SECURITY_CONTEXT_ATTRIBUTES)) {
133+
context = currentContext;
134+
} else {
135+
context = currentContext.put(SECURITY_CONTEXT_ATTRIBUTES, attributes);
136+
}
137+
this.context = context;
138+
}
139+
140+
@Override
141+
public Context currentContext() {
142+
return this.context;
143+
}
144+
145+
@Override
146+
public void onSubscribe(Subscription s) {
147+
this.delegate.onSubscribe(s);
148+
}
149+
150+
@Override
151+
public void onNext(T t) {
152+
this.delegate.onNext(t);
153+
}
154+
155+
@Override
156+
public void onError(Throwable t) {
157+
this.delegate.onError(t);
158+
}
159+
160+
@Override
161+
public void onComplete() {
162+
this.delegate.onComplete();
163+
}
164+
}
165+
}

0 commit comments

Comments
 (0)