Skip to content

Commit d24a89a

Browse files
committed
Pick up SecurityContextHolderStrategy for WebClient integration
Issue gh-11061
1 parent a218d3e commit d24a89a

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.
@@ -36,10 +36,13 @@
3636

3737
import org.springframework.beans.factory.DisposableBean;
3838
import org.springframework.beans.factory.InitializingBean;
39+
import org.springframework.beans.factory.annotation.Autowired;
3940
import org.springframework.context.annotation.Bean;
4041
import org.springframework.context.annotation.Configuration;
4142
import org.springframework.security.core.Authentication;
4243
import org.springframework.security.core.context.SecurityContextHolder;
44+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
45+
import org.springframework.util.Assert;
4346
import org.springframework.web.context.request.RequestAttributes;
4447
import org.springframework.web.context.request.RequestContextHolder;
4548
import org.springframework.web.context.request.ServletRequestAttributes;
@@ -61,24 +64,37 @@
6164
@Configuration(proxyBeanMethods = false)
6265
class SecurityReactorContextConfiguration {
6366

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

6983
static class SecurityReactorContextSubscriberRegistrar implements InitializingBean, DisposableBean {
7084

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

73-
private static final Map<Object, Supplier<Object>> CONTEXT_ATTRIBUTE_VALUE_LOADERS = new HashMap<>();
87+
private final Map<Object, Supplier<Object>> CONTEXT_ATTRIBUTE_VALUE_LOADERS = new HashMap<>();
7488

75-
static {
76-
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletRequest.class,
89+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
90+
.getContextHolderStrategy();
91+
92+
SecurityReactorContextSubscriberRegistrar() {
93+
this.CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletRequest.class,
7794
SecurityReactorContextSubscriberRegistrar::getRequest);
78-
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletResponse.class,
95+
this.CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletResponse.class,
7996
SecurityReactorContextSubscriberRegistrar::getResponse);
80-
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(Authentication.class,
81-
SecurityReactorContextSubscriberRegistrar::getAuthentication);
97+
this.CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(Authentication.class, this::getAuthentication);
8298
}
8399

84100
@Override
@@ -93,6 +109,11 @@ public void destroy() throws Exception {
93109
Hooks.resetOnLastOperator(SECURITY_REACTOR_CONTEXT_OPERATOR_KEY);
94110
}
95111

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

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

108129
private static HttpServletRequest getRequest() {
@@ -123,8 +144,8 @@ private static HttpServletResponse getResponse() {
123144
return null;
124145
}
125146

126-
private static Authentication getAuthentication() {
127-
return SecurityContextHolder.getContext().getAuthentication();
147+
private Authentication getAuthentication() {
148+
return this.securityContextHolderStrategy.getContext().getAuthentication();
128149
}
129150

130151
}

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.
@@ -28,9 +28,11 @@
2828
import org.springframework.beans.factory.annotation.Autowired;
2929
import org.springframework.context.annotation.Bean;
3030
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
3132
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3233
import org.springframework.security.config.test.SpringTestContext;
3334
import org.springframework.security.config.test.SpringTestContextExtension;
35+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3436
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
3537
import org.springframework.security.oauth2.server.resource.authentication.TestBearerTokenAuthentications;
3638
import org.springframework.security.oauth2.server.resource.web.reactive.function.client.ServletBearerExchangeFilterFunction;
@@ -40,6 +42,8 @@
4042
import org.springframework.web.bind.annotation.RestController;
4143
import org.springframework.web.reactive.function.client.WebClient;
4244

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

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

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.
@@ -38,12 +38,14 @@
3838
import org.springframework.mock.web.MockHttpServletRequest;
3939
import org.springframework.mock.web.MockHttpServletResponse;
4040
import org.springframework.security.authentication.TestingAuthenticationToken;
41+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
4142
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4243
import org.springframework.security.config.annotation.web.configuration.SecurityReactorContextConfiguration.SecurityReactorContextSubscriber;
4344
import org.springframework.security.config.test.SpringTestContext;
4445
import org.springframework.security.config.test.SpringTestContextExtension;
4546
import org.springframework.security.core.Authentication;
4647
import org.springframework.security.core.context.SecurityContextHolder;
48+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
4749
import org.springframework.security.oauth2.client.web.reactive.function.client.MockExchangeFunction;
4850
import org.springframework.web.context.request.RequestAttributes;
4951
import org.springframework.web.context.request.RequestContextHolder;
@@ -54,6 +56,8 @@
5456

5557
import static org.assertj.core.api.Assertions.assertThat;
5658
import static org.assertj.core.api.Assertions.entry;
59+
import static org.mockito.Mockito.times;
60+
import static org.mockito.Mockito.verify;
5761

5862
/**
5963
* Tests for {@link SecurityReactorContextConfiguration}.
@@ -232,6 +236,38 @@ public void createPublisherWhenLastOperatorAddedThenSecurityContextAttributesAva
232236
// @formatter:on
233237
}
234238

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

0 commit comments

Comments
 (0)