Skip to content

Commit e8723f1

Browse files
committed
Pick up SecurityContextHolderStrategy for WebClient integration
Issue gh-11061
1 parent 27de315 commit e8723f1

File tree

3 files changed

+90
-14
lines changed

3 files changed

+90
-14
lines changed

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

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -37,10 +37,13 @@
3737

3838
import org.springframework.beans.factory.DisposableBean;
3939
import org.springframework.beans.factory.InitializingBean;
40+
import org.springframework.beans.factory.annotation.Autowired;
4041
import org.springframework.context.annotation.Bean;
4142
import org.springframework.context.annotation.Configuration;
4243
import org.springframework.security.core.Authentication;
4344
import org.springframework.security.core.context.SecurityContextHolder;
45+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
46+
import org.springframework.util.Assert;
4447
import org.springframework.web.context.request.RequestAttributes;
4548
import org.springframework.web.context.request.RequestContextHolder;
4649
import org.springframework.web.context.request.ServletRequestAttributes;
@@ -62,24 +65,37 @@
6265
@Configuration(proxyBeanMethods = false)
6366
class SecurityReactorContextConfiguration {
6467

68+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
69+
.getContextHolderStrategy();
70+
6571
@Bean
6672
SecurityReactorContextSubscriberRegistrar securityReactorContextSubscriberRegistrar() {
67-
return new SecurityReactorContextSubscriberRegistrar();
73+
SecurityReactorContextSubscriberRegistrar registrar = new SecurityReactorContextSubscriberRegistrar();
74+
registrar.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
75+
return registrar;
76+
}
77+
78+
@Autowired(required = false)
79+
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
80+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
81+
this.securityContextHolderStrategy = securityContextHolderStrategy;
6882
}
6983

7084
static class SecurityReactorContextSubscriberRegistrar implements InitializingBean, DisposableBean {
7185

7286
private static final String SECURITY_REACTOR_CONTEXT_OPERATOR_KEY = "org.springframework.security.SECURITY_REACTOR_CONTEXT_OPERATOR";
7387

74-
private static final Map<Object, Supplier<Object>> CONTEXT_ATTRIBUTE_VALUE_LOADERS = new HashMap<>();
88+
private final Map<Object, Supplier<Object>> CONTEXT_ATTRIBUTE_VALUE_LOADERS = new HashMap<>();
7589

76-
static {
77-
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletRequest.class,
90+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
91+
.getContextHolderStrategy();
92+
93+
SecurityReactorContextSubscriberRegistrar() {
94+
this.CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletRequest.class,
7895
SecurityReactorContextSubscriberRegistrar::getRequest);
79-
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletResponse.class,
96+
this.CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletResponse.class,
8097
SecurityReactorContextSubscriberRegistrar::getResponse);
81-
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(Authentication.class,
82-
SecurityReactorContextSubscriberRegistrar::getAuthentication);
98+
this.CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(Authentication.class, this::getAuthentication);
8399
}
84100

85101
@Override
@@ -94,6 +110,11 @@ public void destroy() throws Exception {
94110
Hooks.resetOnLastOperator(SECURITY_REACTOR_CONTEXT_OPERATOR_KEY);
95111
}
96112

113+
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
114+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
115+
this.securityContextHolderStrategy = securityContextHolderStrategy;
116+
}
117+
97118
<T> CoreSubscriber<T> createSubscriberIfNecessary(CoreSubscriber<T> delegate) {
98119
if (delegate.currentContext().hasKey(SecurityReactorContextSubscriber.SECURITY_CONTEXT_ATTRIBUTES)) {
99120
// Already enriched. No need to create Subscriber so return original
@@ -102,8 +123,8 @@ <T> CoreSubscriber<T> createSubscriberIfNecessary(CoreSubscriber<T> delegate) {
102123
return new SecurityReactorContextSubscriber<>(delegate, getContextAttributes());
103124
}
104125

105-
private static Map<Object, Object> getContextAttributes() {
106-
return new LoadingMap<>(CONTEXT_ATTRIBUTE_VALUE_LOADERS);
126+
private Map<Object, Object> getContextAttributes() {
127+
return new LoadingMap<>(this.CONTEXT_ATTRIBUTE_VALUE_LOADERS);
107128
}
108129

109130
private static HttpServletRequest getRequest() {
@@ -124,8 +145,8 @@ private static HttpServletResponse getResponse() {
124145
return null;
125146
}
126147

127-
private static Authentication getAuthentication() {
128-
return SecurityContextHolder.getContext().getAuthentication();
148+
private Authentication getAuthentication() {
149+
return this.securityContextHolderStrategy.getContext().getAuthentication();
129150
}
130151

131152
}

config/src/test/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfigurationResourceServerTests.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -29,9 +29,11 @@
2929
import org.springframework.beans.factory.annotation.Autowired;
3030
import org.springframework.context.annotation.Bean;
3131
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
3233
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3334
import org.springframework.security.config.test.SpringTestContext;
3435
import org.springframework.security.config.test.SpringTestContextExtension;
36+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3537
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
3638
import org.springframework.security.oauth2.server.resource.authentication.TestBearerTokenAuthentications;
3739
import org.springframework.security.oauth2.server.resource.web.reactive.function.client.ServletBearerExchangeFilterFunction;
@@ -41,6 +43,8 @@
4143
import org.springframework.web.bind.annotation.RestController;
4244
import org.springframework.web.reactive.function.client.WebClient;
4345

46+
import static org.mockito.Mockito.atLeastOnce;
47+
import static org.mockito.Mockito.verify;
4448
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
4549
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
4650
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -86,6 +90,21 @@ public void requestWhenNotUsingFilterThenBearerTokenNotPropagated() throws Excep
8690
// @formatter:on
8791
}
8892

93+
@Test
94+
public void requestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
95+
BearerTokenAuthentication authentication = TestBearerTokenAuthentications.bearer();
96+
this.spring.register(BearerFilterConfig.class, WebServerConfig.class, Controller.class,
97+
SecurityContextChangedListenerConfig.class).autowire();
98+
MockHttpServletRequestBuilder authenticatedRequest = get("/token").with(authentication(authentication));
99+
// @formatter:off
100+
this.mockMvc.perform(authenticatedRequest)
101+
.andExpect(status().isOk())
102+
.andExpect(content().string("Bearer token"));
103+
// @formatter:on
104+
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
105+
verify(strategy, atLeastOnce()).getContext();
106+
}
107+
89108
@EnableWebSecurity
90109
static class BearerFilterConfig extends WebSecurityConfigurerAdapter {
91110

config/src/test/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfigurationTests.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -39,12 +39,14 @@
3939
import org.springframework.mock.web.MockHttpServletRequest;
4040
import org.springframework.mock.web.MockHttpServletResponse;
4141
import org.springframework.security.authentication.TestingAuthenticationToken;
42+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
4243
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4344
import org.springframework.security.config.annotation.web.configuration.SecurityReactorContextConfiguration.SecurityReactorContextSubscriber;
4445
import org.springframework.security.config.test.SpringTestContext;
4546
import org.springframework.security.config.test.SpringTestContextExtension;
4647
import org.springframework.security.core.Authentication;
4748
import org.springframework.security.core.context.SecurityContextHolder;
49+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
4850
import org.springframework.security.oauth2.client.web.reactive.function.client.MockExchangeFunction;
4951
import org.springframework.web.context.request.RequestAttributes;
5052
import org.springframework.web.context.request.RequestContextHolder;
@@ -55,6 +57,8 @@
5557

5658
import static org.assertj.core.api.Assertions.assertThat;
5759
import static org.assertj.core.api.Assertions.entry;
60+
import static org.mockito.Mockito.times;
61+
import static org.mockito.Mockito.verify;
5862

5963
/**
6064
* Tests for {@link SecurityReactorContextConfiguration}.
@@ -233,6 +237,38 @@ public void createPublisherWhenLastOperatorAddedThenSecurityContextAttributesAva
233237
// @formatter:on
234238
}
235239

240+
@Test
241+
public void createPublisherWhenCustomSecurityContextHolderStrategyThenUses() {
242+
this.spring.register(SecurityConfig.class, SecurityContextChangedListenerConfig.class).autowire();
243+
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
244+
strategy.getContext().setAuthentication(this.authentication);
245+
ClientResponse clientResponseOk = ClientResponse.create(HttpStatus.OK).build();
246+
// @formatter:off
247+
ExchangeFilterFunction filter = (req, next) -> Mono.deferContextual(Mono::just)
248+
.filter((ctx) -> ctx.hasKey(SecurityReactorContextSubscriber.SECURITY_CONTEXT_ATTRIBUTES))
249+
.map((ctx) -> ctx.get(SecurityReactorContextSubscriber.SECURITY_CONTEXT_ATTRIBUTES))
250+
.cast(Map.class)
251+
.map((attributes) -> clientResponseOk);
252+
// @formatter:on
253+
ClientRequest clientRequest = ClientRequest.create(HttpMethod.GET, URI.create("https://example.com")).build();
254+
MockExchangeFunction exchange = new MockExchangeFunction();
255+
Map<Object, Object> expectedContextAttributes = new HashMap<>();
256+
expectedContextAttributes.put(HttpServletRequest.class, null);
257+
expectedContextAttributes.put(HttpServletResponse.class, null);
258+
expectedContextAttributes.put(Authentication.class, this.authentication);
259+
Mono<ClientResponse> clientResponseMono = filter.filter(clientRequest, exchange)
260+
.flatMap((response) -> filter.filter(clientRequest, exchange));
261+
// @formatter:off
262+
StepVerifier.create(clientResponseMono)
263+
.expectAccessibleContext()
264+
.contains(SecurityReactorContextSubscriber.SECURITY_CONTEXT_ATTRIBUTES, expectedContextAttributes)
265+
.then()
266+
.expectNext(clientResponseOk)
267+
.verifyComplete();
268+
// @formatter:on
269+
verify(strategy, times(2)).getContext();
270+
}
271+
236272
@EnableWebSecurity
237273
static class SecurityConfig extends WebSecurityConfigurerAdapter {
238274

0 commit comments

Comments
 (0)