Skip to content

Commit bffe084

Browse files
committed
Add SecurityContextHolderStrategy XML Configuration for Messaging
Issue gh-11061
1 parent 484f35c commit bffe084

File tree

8 files changed

+110
-3
lines changed

8 files changed

+110
-3
lines changed

config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java

+29
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.springframework.beans.BeansException;
2727
import org.springframework.beans.PropertyValue;
28+
import org.springframework.beans.factory.FactoryBean;
2829
import org.springframework.beans.factory.config.BeanDefinition;
2930
import org.springframework.beans.factory.config.BeanReference;
3031
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -50,6 +51,8 @@
5051
import org.springframework.security.authorization.AuthorizationManager;
5152
import org.springframework.security.config.Elements;
5253
import org.springframework.security.core.Authentication;
54+
import org.springframework.security.core.context.SecurityContextHolder;
55+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
5356
import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory;
5457
import org.springframework.security.messaging.access.expression.MessageAuthorizationContextSecurityExpressionHandler;
5558
import org.springframework.security.messaging.access.expression.MessageExpressionVoter;
@@ -118,6 +121,8 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
118121

119122
private static final String AUTHORIZATION_MANAGER_REF_ATTR = "authorization-manager-ref";
120123

124+
private static final String SECURITY_CONTEXT_HOLDER_STRATEGY_REF_ATTR = "security-context-holder-strategy-ref";
125+
121126
private static final String PATTERN_ATTR = "pattern";
122127

123128
private static final String ACCESS_ATTR = "access";
@@ -170,6 +175,16 @@ private String parseAuthorizationManager(Element element, ParserContext parserCo
170175
BeanDefinitionBuilder inboundChannelSecurityInterceptor = BeanDefinitionBuilder
171176
.rootBeanDefinition(AuthorizationChannelInterceptor.class);
172177
inboundChannelSecurityInterceptor.addConstructorArgReference(mdsId);
178+
String holderStrategyRef = element.getAttribute(SECURITY_CONTEXT_HOLDER_STRATEGY_REF_ATTR);
179+
if (StringUtils.hasText(holderStrategyRef)) {
180+
inboundChannelSecurityInterceptor.addPropertyValue("securityContextHolderStrategy",
181+
new RuntimeBeanReference(holderStrategyRef));
182+
}
183+
else {
184+
inboundChannelSecurityInterceptor.addPropertyValue("securityContextHolderStrategy", BeanDefinitionBuilder
185+
.rootBeanDefinition(SecurityContextHolderStrategyFactory.class).getBeanDefinition());
186+
}
187+
173188
return context.registerWithGeneratedName(inboundChannelSecurityInterceptor.getBeanDefinition());
174189
}
175190

@@ -459,4 +474,18 @@ private static AuthorizationManager<Message<?>> createMessageMatcherDelegatingAu
459474

460475
}
461476

477+
static class SecurityContextHolderStrategyFactory implements FactoryBean<SecurityContextHolderStrategy> {
478+
479+
@Override
480+
public SecurityContextHolderStrategy getObject() throws Exception {
481+
return SecurityContextHolder.getContextHolderStrategy();
482+
}
483+
484+
@Override
485+
public Class<?> getObjectType() {
486+
return SecurityContextHolderStrategy.class;
487+
}
488+
489+
}
490+
462491
}

config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc

+3
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ websocket-message-broker.attrlist &=
300300
websocket-message-broker.attrlist &=
301301
## Use AuthorizationManager API instead of SecurityMetadatasource
302302
attribute use-authorization-manager {xsd:boolean}?
303+
websocket-message-broker.attrlist &=
304+
## Use this SecurityContextHolderStrategy (note only supported in conjunction with the AuthorizationManager API)
305+
attribute security-context-holder-strategy-ref {xsd:string}?
303306

304307
intercept-message =
305308
## Creates an authorization rule for a websocket message.

config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd

+7
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,13 @@
934934
</xs:documentation>
935935
</xs:annotation>
936936
</xs:attribute>
937+
<xs:attribute name="security-context-holder-strategy-ref" type="xs:string">
938+
<xs:annotation>
939+
<xs:documentation>Use this SecurityContextHolderStrategy (note only supported in conjunction with the
940+
AuthorizationManager API)
941+
</xs:documentation>
942+
</xs:annotation>
943+
</xs:attribute>
937944
</xs:attributeGroup>
938945
<xs:element name="intercept-message">
939946
<xs:annotation>

config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc

+3
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ websocket-message-broker.attrlist &=
300300
websocket-message-broker.attrlist &=
301301
## Use AuthorizationManager API instead of SecurityMetadatasource
302302
attribute use-authorization-manager {xsd:boolean}?
303+
websocket-message-broker.attrlist &=
304+
## Use this SecurityContextHolderStrategy (note only supported in conjunction with the AuthorizationManager API)
305+
attribute security-context-holder-strategy-ref {xsd:string}?
303306

304307
intercept-message =
305308
## Creates an authorization rule for a websocket message.

config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd

+7
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,13 @@
934934
</xs:documentation>
935935
</xs:annotation>
936936
</xs:attribute>
937+
<xs:attribute name="security-context-holder-strategy-ref" type="xs:string">
938+
<xs:annotation>
939+
<xs:documentation>Use this SecurityContextHolderStrategy (note only supported in conjunction with the
940+
AuthorizationManager API)
941+
</xs:documentation>
942+
</xs:annotation>
943+
</xs:attribute>
937944
</xs:attributeGroup>
938945
<xs:element name="intercept-message">
939946
<xs:annotation>

config/src/test/java/org/springframework/security/config/websocket/WebSocketMessageBrokerConfigTests.java

+22-3
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-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.
@@ -54,6 +54,7 @@
5454
import org.springframework.security.core.Authentication;
5555
import org.springframework.security.core.annotation.AuthenticationPrincipal;
5656
import org.springframework.security.core.context.SecurityContextHolder;
57+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
5758
import org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler;
5859
import org.springframework.security.messaging.access.expression.MessageSecurityExpressionRoot;
5960
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
@@ -248,6 +249,15 @@ public void sendWhenAnonymousMessageWithUnsubscribeMessageTypeThenAuthorizationM
248249
send(message);
249250
}
250251

252+
@Test
253+
public void sendWhenAnonymousMessageWithCustomSecurityContextHolderStrategyAndAuthorizationManagerThenUses() {
254+
this.spring.configLocations(xml("WithSecurityContextHolderStrategy")).autowire();
255+
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
256+
Message<?> message = message("/authenticated", SimpMessageType.CONNECT);
257+
send(message);
258+
verify(strategy).getContext();
259+
}
260+
251261
@Test
252262
public void sendWhenConnectWithoutCsrfTokenThenDenied() {
253263
this.spring.configLocations(xml("SyncConfig")).autowire();
@@ -500,13 +510,22 @@ private Message<?> message(String destination, SimpMessageHeaderAccessor headers
500510
headers.setSessionId("123");
501511
headers.setSessionAttributes(new HashMap<>());
502512
headers.setDestination(destination);
503-
if (SecurityContextHolder.getContext().getAuthentication() != null) {
504-
headers.setUser(SecurityContextHolder.getContext().getAuthentication());
513+
SecurityContextHolderStrategy strategy = getSecurityContextHolderStrategy();
514+
if (strategy.getContext().getAuthentication() != null) {
515+
headers.setUser(strategy.getContext().getAuthentication());
505516
}
506517
headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
507518
return new GenericMessage<>("hi", headers.getMessageHeaders());
508519
}
509520

521+
private SecurityContextHolderStrategy getSecurityContextHolderStrategy() {
522+
String[] names = this.spring.getContext().getBeanNamesForType(SecurityContextHolderStrategy.class);
523+
if (names.length == 1) {
524+
return this.spring.getContext().getBean(names[0], SecurityContextHolderStrategy.class);
525+
}
526+
return SecurityContextHolder.getContextHolderStrategy();
527+
}
528+
510529
@Controller
511530
static class MessageController {
512531

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2002-2022 the original author or authors.
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ https://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
18+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19+
xmlns="http://www.springframework.org/schema/security"
20+
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
21+
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
22+
23+
<b:import resource="classpath:org/springframework/security/config/websocket/controllers.xml"/>
24+
<b:import resource="classpath:org/springframework/security/config/websocket/websocket.xml"/>
25+
26+
<websocket-message-broker use-authorization-manager="true" security-context-holder-strategy-ref="ref">
27+
<intercept-message pattern="/authenticated" access="authenticated"/>
28+
</websocket-message-broker>
29+
30+
<b:bean id="ref" class="org.mockito.Mockito" factory-method="spy">
31+
<b:constructor-arg>
32+
<b:bean class="org.springframework.security.config.MockSecurityContextHolderStrategy"/>
33+
</b:constructor-arg>
34+
</b:bean>
35+
36+
</b:beans>

docs/modules/ROOT/pages/servlet/appendix/namespace/websocket.adoc

+3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ Changing the default is useful if it is necessary to allow other origins to make
4444
[[nsa-websocket-message-broker-use-authorization-manager]]
4545
* **use-authorization-manager** Uses legacy `SecurityMetadataSource` API instead of `AuthorizationManager` API (default false).
4646

47+
[[nsa-websocket-message-broker-security-context-holder-strategy-ref]]
48+
* **security-context-holder-strategy-ref** Use this `SecurityContextHolderStrategy` (note only supported in conjunction with the `AuthorizationManager` API)
49+
4750
[[nsa-websocket-message-broker-children]]
4851
=== Child Elements of <websocket-message-broker>
4952

0 commit comments

Comments
 (0)