Skip to content

Commit bc0d706

Browse files
committed
Use PathPatternMessageMatcher.Builder in XML Config
Closes gh-17508
1 parent 9209a33 commit bc0d706

File tree

6 files changed

+206
-17
lines changed

6 files changed

+206
-17
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2002-2025 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+
17+
package org.springframework.security.config.http;
18+
19+
import org.springframework.beans.BeansException;
20+
import org.springframework.beans.factory.FactoryBean;
21+
import org.springframework.context.ApplicationContext;
22+
import org.springframework.context.ApplicationContextAware;
23+
import org.springframework.messaging.simp.SimpMessageType;
24+
import org.springframework.security.messaging.util.matcher.MessageMatcher;
25+
import org.springframework.security.messaging.util.matcher.PathPatternMessageMatcher;
26+
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
27+
import org.springframework.util.AntPathMatcher;
28+
import org.springframework.util.PathMatcher;
29+
30+
@Deprecated
31+
public final class MessageMatcherFactoryBean implements FactoryBean<MessageMatcher<?>>, ApplicationContextAware {
32+
33+
private PathPatternMessageMatcher.Builder builder;
34+
35+
private final SimpMessageType method;
36+
37+
private final String path;
38+
39+
private PathMatcher pathMatcher = new AntPathMatcher();
40+
41+
public MessageMatcherFactoryBean(String path) {
42+
this(path, null);
43+
}
44+
45+
public MessageMatcherFactoryBean(String path, SimpMessageType method) {
46+
this.method = method;
47+
this.path = path;
48+
}
49+
50+
@Override
51+
public MessageMatcher<?> getObject() throws Exception {
52+
if (this.builder != null) {
53+
return this.builder.matcher(this.method, this.path);
54+
}
55+
if (this.method == SimpMessageType.SUBSCRIBE) {
56+
return SimpDestinationMessageMatcher.createSubscribeMatcher(this.path, this.pathMatcher);
57+
}
58+
if (this.method == SimpMessageType.MESSAGE) {
59+
return SimpDestinationMessageMatcher.createMessageMatcher(this.path, this.pathMatcher);
60+
}
61+
return new SimpDestinationMessageMatcher(this.path, this.pathMatcher);
62+
}
63+
64+
@Override
65+
public Class<?> getObjectType() {
66+
return null;
67+
}
68+
69+
public void setPathMatcher(PathMatcher pathMatcher) {
70+
this.pathMatcher = pathMatcher;
71+
}
72+
73+
@Override
74+
public void setApplicationContext(ApplicationContext context) throws BeansException {
75+
this.builder = context.getBeanProvider(PathPatternMessageMatcher.Builder.class).getIfUnique();
76+
}
77+
78+
}

config/src/main/java/org/springframework/security/config/web/messaging/PathPatternMessageMatcherBuilderFactoryBean.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.springframework.beans.factory.FactoryBean;
2020
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
2121
import org.springframework.security.messaging.util.matcher.PathPatternMessageMatcher;
22+
import org.springframework.web.util.pattern.PathPatternParser;
2223

2324
/**
2425
* Use this factory bean to configure the {@link PathPatternMessageMatcher.Builder} bean
@@ -31,9 +32,30 @@
3132
public final class PathPatternMessageMatcherBuilderFactoryBean
3233
implements FactoryBean<PathPatternMessageMatcher.Builder> {
3334

35+
private PathPatternParser parser;
36+
37+
/**
38+
* Create {@link PathPatternMessageMatcher}s using
39+
* {@link PathPatternParser#defaultInstance}
40+
*/
41+
public PathPatternMessageMatcherBuilderFactoryBean() {
42+
43+
}
44+
45+
/**
46+
* Create {@link PathPatternMessageMatcher}s using the given {@link PathPatternParser}
47+
* @param parser the {@link PathPatternParser} to use
48+
*/
49+
public PathPatternMessageMatcherBuilderFactoryBean(PathPatternParser parser) {
50+
this.parser = parser;
51+
}
52+
3453
@Override
3554
public PathPatternMessageMatcher.Builder getObject() throws Exception {
36-
return PathPatternMessageMatcher.withDefaults();
55+
if (this.parser == null) {
56+
return PathPatternMessageMatcher.withDefaults();
57+
}
58+
return PathPatternMessageMatcher.withPathPatternParser(this.parser);
3759
}
3860

3961
@Override

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

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -50,6 +50,7 @@
5050
import org.springframework.security.authorization.AuthorizationDecision;
5151
import org.springframework.security.authorization.AuthorizationManager;
5252
import org.springframework.security.config.Elements;
53+
import org.springframework.security.config.http.MessageMatcherFactoryBean;
5354
import org.springframework.security.core.Authentication;
5455
import org.springframework.security.core.context.SecurityContextHolder;
5556
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -63,7 +64,6 @@
6364
import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
6465
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
6566
import org.springframework.security.messaging.util.matcher.MessageMatcher;
66-
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
6767
import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher;
6868
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
6969
import org.springframework.security.messaging.web.socket.server.CsrfTokenHandshakeInterceptor;
@@ -270,25 +270,18 @@ private BeanDefinition createMatcher(String matcherPattern, String messageType,
270270
matcher.addConstructorArgValue(messageType);
271271
return matcher.getBeanDefinition();
272272
}
273-
String factoryName = null;
274-
if (hasPattern && hasMessageType) {
273+
BeanDefinitionBuilder matcher = BeanDefinitionBuilder.rootBeanDefinition(MessageMatcherFactoryBean.class);
274+
matcher.addConstructorArgValue(matcherPattern);
275+
if (hasMessageType) {
275276
SimpMessageType type = SimpMessageType.valueOf(messageType);
276-
if (SimpMessageType.MESSAGE == type) {
277-
factoryName = "createMessageMatcher";
278-
}
279-
else if (SimpMessageType.SUBSCRIBE == type) {
280-
factoryName = "createSubscribeMatcher";
281-
}
282-
else {
277+
matcher.addConstructorArgValue(type);
278+
if (SimpMessageType.SUBSCRIBE != type && SimpMessageType.MESSAGE != type) {
283279
parserContext.getReaderContext()
284280
.error("Cannot use intercept-websocket@message-type=" + messageType
285281
+ " with a pattern because the type does not have a destination.", interceptMessage);
286282
}
287283
}
288-
BeanDefinitionBuilder matcher = BeanDefinitionBuilder.rootBeanDefinition(SimpDestinationMessageMatcher.class);
289-
matcher.setFactoryMethod(factoryName);
290-
matcher.addConstructorArgValue(matcherPattern);
291-
matcher.addConstructorArgValue(new RuntimeBeanReference("springSecurityMessagePathMatcher"));
284+
matcher.addPropertyValue("pathMatcher", new RuntimeBeanReference("springSecurityMessagePathMatcher"));
292285
return matcher.getBeanDefinition();
293286
}
294287

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -334,6 +334,32 @@ public void sendWhenInterceptWiredForSubscribeTypeThenAuthorizationManagerDenies
334334
.withCauseInstanceOf(AccessDeniedException.class);
335335
}
336336

337+
@Test
338+
public void sendWhenPathPatternFactoryBeanThenConstructsPatternsWithPathPattern() {
339+
this.spring.configLocations(xml("SubscribeInterceptTypePathPattern")).autowire();
340+
Message<?> message = message("/permitAll", SimpMessageType.SUBSCRIBE);
341+
send(message);
342+
message = message("/permitAll", SimpMessageType.UNSUBSCRIBE);
343+
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
344+
.withCauseInstanceOf(AccessDeniedException.class);
345+
message = message("/anyOther", SimpMessageType.SUBSCRIBE);
346+
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
347+
.withCauseInstanceOf(AccessDeniedException.class);
348+
}
349+
350+
@Test
351+
public void sendWhenCaseInsensitivePathPatternParserThenMatchesMixedCase() {
352+
this.spring.configLocations(xml("SubscribeInterceptTypePathPatternParser")).autowire();
353+
Message<?> message = message("/peRmItAll", SimpMessageType.SUBSCRIBE);
354+
send(message);
355+
message = message("/peRmKtAll", SimpMessageType.UNSUBSCRIBE);
356+
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
357+
.withCauseInstanceOf(AccessDeniedException.class);
358+
message = message("/aNyOtHer", SimpMessageType.SUBSCRIBE);
359+
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
360+
.withCauseInstanceOf(AccessDeniedException.class);
361+
}
362+
337363
@Test
338364
public void configureWhenUsingConnectMessageTypeThenAutowireFails() {
339365
assertThatExceptionOfType(BeanDefinitionParsingException.class)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2002-2018 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>
27+
<intercept-message pattern="/permitAll" type="SUBSCRIBE" access="permitAll"/>
28+
<intercept-message pattern="/**" access="denyAll"/>
29+
</websocket-message-broker>
30+
31+
<b:bean class="org.springframework.security.config.web.messaging.PathPatternMessageMatcherBuilderFactoryBean"/>
32+
</b:beans>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2002-2018 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>
27+
<intercept-message pattern="/permitAll" type="SUBSCRIBE" access="permitAll"/>
28+
<intercept-message pattern="/**" access="denyAll"/>
29+
</websocket-message-broker>
30+
31+
<b:bean name="pathPatternParser" class="org.springframework.web.util.pattern.PathPatternParser">
32+
<b:property name="caseSensitive" value="false"/>
33+
</b:bean>
34+
35+
<b:bean class="org.springframework.security.config.web.messaging.PathPatternMessageMatcherBuilderFactoryBean">
36+
<b:constructor-arg ref="pathPatternParser"/>
37+
</b:bean>
38+
</b:beans>

0 commit comments

Comments
 (0)