소스 검색

Use PathPatternMessageMatcher.Builder in XML Config

Closes gh-17508
Josh Cummings 1 개월 전
부모
커밋
bc0d706275

+ 78 - 0
config/src/main/java/org/springframework/security/config/http/MessageMatcherFactoryBean.java

@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.config.http;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.messaging.simp.SimpMessageType;
+import org.springframework.security.messaging.util.matcher.MessageMatcher;
+import org.springframework.security.messaging.util.matcher.PathPatternMessageMatcher;
+import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+
+@Deprecated
+public final class MessageMatcherFactoryBean implements FactoryBean<MessageMatcher<?>>, ApplicationContextAware {
+
+	private PathPatternMessageMatcher.Builder builder;
+
+	private final SimpMessageType method;
+
+	private final String path;
+
+	private PathMatcher pathMatcher = new AntPathMatcher();
+
+	public MessageMatcherFactoryBean(String path) {
+		this(path, null);
+	}
+
+	public MessageMatcherFactoryBean(String path, SimpMessageType method) {
+		this.method = method;
+		this.path = path;
+	}
+
+	@Override
+	public MessageMatcher<?> getObject() throws Exception {
+		if (this.builder != null) {
+			return this.builder.matcher(this.method, this.path);
+		}
+		if (this.method == SimpMessageType.SUBSCRIBE) {
+			return SimpDestinationMessageMatcher.createSubscribeMatcher(this.path, this.pathMatcher);
+		}
+		if (this.method == SimpMessageType.MESSAGE) {
+			return SimpDestinationMessageMatcher.createMessageMatcher(this.path, this.pathMatcher);
+		}
+		return new SimpDestinationMessageMatcher(this.path, this.pathMatcher);
+	}
+
+	@Override
+	public Class<?> getObjectType() {
+		return null;
+	}
+
+	public void setPathMatcher(PathMatcher pathMatcher) {
+		this.pathMatcher = pathMatcher;
+	}
+
+	@Override
+	public void setApplicationContext(ApplicationContext context) throws BeansException {
+		this.builder = context.getBeanProvider(PathPatternMessageMatcher.Builder.class).getIfUnique();
+	}
+
+}

+ 23 - 1
config/src/main/java/org/springframework/security/config/web/messaging/PathPatternMessageMatcherBuilderFactoryBean.java

@@ -19,6 +19,7 @@ package org.springframework.security.config.web.messaging;
 import org.springframework.beans.factory.FactoryBean;
 import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
 import org.springframework.security.messaging.util.matcher.PathPatternMessageMatcher;
+import org.springframework.web.util.pattern.PathPatternParser;
 
 /**
  * Use this factory bean to configure the {@link PathPatternMessageMatcher.Builder} bean
@@ -31,9 +32,30 @@ import org.springframework.security.messaging.util.matcher.PathPatternMessageMat
 public final class PathPatternMessageMatcherBuilderFactoryBean
 		implements FactoryBean<PathPatternMessageMatcher.Builder> {
 
+	private PathPatternParser parser;
+
+	/**
+	 * Create {@link PathPatternMessageMatcher}s using
+	 * {@link PathPatternParser#defaultInstance}
+	 */
+	public PathPatternMessageMatcherBuilderFactoryBean() {
+
+	}
+
+	/**
+	 * Create {@link PathPatternMessageMatcher}s using the given {@link PathPatternParser}
+	 * @param parser the {@link PathPatternParser} to use
+	 */
+	public PathPatternMessageMatcherBuilderFactoryBean(PathPatternParser parser) {
+		this.parser = parser;
+	}
+
 	@Override
 	public PathPatternMessageMatcher.Builder getObject() throws Exception {
-		return PathPatternMessageMatcher.withDefaults();
+		if (this.parser == null) {
+			return PathPatternMessageMatcher.withDefaults();
+		}
+		return PathPatternMessageMatcher.withPathPatternParser(this.parser);
 	}
 
 	@Override

+ 8 - 15
config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@ import org.springframework.security.access.vote.ConsensusBased;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationManager;
 import org.springframework.security.config.Elements;
+import org.springframework.security.config.http.MessageMatcherFactoryBean;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -63,7 +64,6 @@ import org.springframework.security.messaging.access.intercept.MessageMatcherDel
 import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
 import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
 import org.springframework.security.messaging.util.matcher.MessageMatcher;
-import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
 import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher;
 import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
 import org.springframework.security.messaging.web.socket.server.CsrfTokenHandshakeInterceptor;
@@ -270,25 +270,18 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
 			matcher.addConstructorArgValue(messageType);
 			return matcher.getBeanDefinition();
 		}
-		String factoryName = null;
-		if (hasPattern && hasMessageType) {
+		BeanDefinitionBuilder matcher = BeanDefinitionBuilder.rootBeanDefinition(MessageMatcherFactoryBean.class);
+		matcher.addConstructorArgValue(matcherPattern);
+		if (hasMessageType) {
 			SimpMessageType type = SimpMessageType.valueOf(messageType);
-			if (SimpMessageType.MESSAGE == type) {
-				factoryName = "createMessageMatcher";
-			}
-			else if (SimpMessageType.SUBSCRIBE == type) {
-				factoryName = "createSubscribeMatcher";
-			}
-			else {
+			matcher.addConstructorArgValue(type);
+			if (SimpMessageType.SUBSCRIBE != type && SimpMessageType.MESSAGE != type) {
 				parserContext.getReaderContext()
 					.error("Cannot use intercept-websocket@message-type=" + messageType
 							+ " with a pattern because the type does not have a destination.", interceptMessage);
 			}
 		}
-		BeanDefinitionBuilder matcher = BeanDefinitionBuilder.rootBeanDefinition(SimpDestinationMessageMatcher.class);
-		matcher.setFactoryMethod(factoryName);
-		matcher.addConstructorArgValue(matcherPattern);
-		matcher.addConstructorArgValue(new RuntimeBeanReference("springSecurityMessagePathMatcher"));
+		matcher.addPropertyValue("pathMatcher", new RuntimeBeanReference("springSecurityMessagePathMatcher"));
 		return matcher.getBeanDefinition();
 	}
 

+ 27 - 1
config/src/test/java/org/springframework/security/config/websocket/WebSocketMessageBrokerConfigTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -334,6 +334,32 @@ public class WebSocketMessageBrokerConfigTests {
 			.withCauseInstanceOf(AccessDeniedException.class);
 	}
 
+	@Test
+	public void sendWhenPathPatternFactoryBeanThenConstructsPatternsWithPathPattern() {
+		this.spring.configLocations(xml("SubscribeInterceptTypePathPattern")).autowire();
+		Message<?> message = message("/permitAll", SimpMessageType.SUBSCRIBE);
+		send(message);
+		message = message("/permitAll", SimpMessageType.UNSUBSCRIBE);
+		assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
+			.withCauseInstanceOf(AccessDeniedException.class);
+		message = message("/anyOther", SimpMessageType.SUBSCRIBE);
+		assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
+			.withCauseInstanceOf(AccessDeniedException.class);
+	}
+
+	@Test
+	public void sendWhenCaseInsensitivePathPatternParserThenMatchesMixedCase() {
+		this.spring.configLocations(xml("SubscribeInterceptTypePathPatternParser")).autowire();
+		Message<?> message = message("/peRmItAll", SimpMessageType.SUBSCRIBE);
+		send(message);
+		message = message("/peRmKtAll", SimpMessageType.UNSUBSCRIBE);
+		assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
+			.withCauseInstanceOf(AccessDeniedException.class);
+		message = message("/aNyOtHer", SimpMessageType.SUBSCRIBE);
+		assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
+			.withCauseInstanceOf(AccessDeniedException.class);
+	}
+
 	@Test
 	public void configureWhenUsingConnectMessageTypeThenAutowireFails() {
 		assertThatExceptionOfType(BeanDefinitionParsingException.class)

+ 32 - 0
config/src/test/resources/org/springframework/security/config/websocket/WebSocketMessageBrokerConfigTests-SubscribeInterceptTypePathPattern.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2018 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      https://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:import resource="classpath:org/springframework/security/config/websocket/controllers.xml"/>
+	<b:import resource="classpath:org/springframework/security/config/websocket/websocket.xml"/>
+
+	<websocket-message-broker>
+		<intercept-message pattern="/permitAll" type="SUBSCRIBE" access="permitAll"/>
+		<intercept-message pattern="/**" access="denyAll"/>
+	</websocket-message-broker>
+
+	<b:bean class="org.springframework.security.config.web.messaging.PathPatternMessageMatcherBuilderFactoryBean"/>
+</b:beans>

+ 38 - 0
config/src/test/resources/org/springframework/security/config/websocket/WebSocketMessageBrokerConfigTests-SubscribeInterceptTypePathPatternParser.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2018 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      https://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 xmlns="http://www.springframework.org/schema/security"
+		 xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
+		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<b:import resource="classpath:org/springframework/security/config/websocket/controllers.xml"/>
+	<b:import resource="classpath:org/springframework/security/config/websocket/websocket.xml"/>
+
+	<websocket-message-broker>
+		<intercept-message pattern="/permitAll" type="SUBSCRIBE" access="permitAll"/>
+		<intercept-message pattern="/**" access="denyAll"/>
+	</websocket-message-broker>
+
+	<b:bean name="pathPatternParser" class="org.springframework.web.util.pattern.PathPatternParser">
+		<b:property name="caseSensitive" value="false"/>
+	</b:bean>
+
+	<b:bean class="org.springframework.security.config.web.messaging.PathPatternMessageMatcherBuilderFactoryBean">
+		<b:constructor-arg ref="pathPatternParser"/>
+	</b:bean>
+</b:beans>