Skip to content

Commit 542ee2b

Browse files
committed
Create PathPatternMessageMatcher
Signed-off-by: Pat McCusker <patmccusker14@gmail.com>
1 parent f17a969 commit 542ee2b

File tree

7 files changed

+518
-27
lines changed

7 files changed

+518
-27
lines changed

config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationDocTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 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.

messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java

+146-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 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.
@@ -17,6 +17,7 @@
1717
package org.springframework.security.messaging.access.intercept;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.List;
2122
import java.util.Map;
2223
import java.util.function.Supplier;
@@ -25,6 +26,7 @@
2526
import org.apache.commons.logging.LogFactory;
2627

2728
import org.springframework.core.log.LogMessage;
29+
import org.springframework.http.server.PathContainer;
2830
import org.springframework.messaging.Message;
2931
import org.springframework.messaging.simp.SimpMessageType;
3032
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
@@ -33,12 +35,14 @@
3335
import org.springframework.security.authorization.AuthorizationManager;
3436
import org.springframework.security.core.Authentication;
3537
import org.springframework.security.messaging.util.matcher.MessageMatcher;
38+
import org.springframework.security.messaging.util.matcher.PathPatternMessageMatcher;
3639
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
3740
import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher;
3841
import org.springframework.util.AntPathMatcher;
3942
import org.springframework.util.Assert;
4043
import org.springframework.util.PathMatcher;
4144
import org.springframework.util.function.SingletonSupplier;
45+
import org.springframework.web.util.pattern.PathPatternParser;
4246

4347
public final class MessageMatcherDelegatingAuthorizationManager implements AuthorizationManager<Message<?>> {
4448

@@ -87,12 +91,11 @@ private MessageAuthorizationContext<?> authorizationContext(MessageMatcher<?> ma
8791
if (!matcher.matches((Message) message)) {
8892
return null;
8993
}
90-
if (matcher instanceof SimpDestinationMessageMatcher simp) {
91-
return new MessageAuthorizationContext<>(message, simp.extractPathVariables(message));
94+
if (matcher instanceof Builder.LazySimpDestinationMessageMatcher pathMatcher) {
95+
return new MessageAuthorizationContext<>(message, pathMatcher.extractPathVariables(message));
9296
}
93-
if (matcher instanceof Builder.LazySimpDestinationMessageMatcher) {
94-
Builder.LazySimpDestinationMessageMatcher path = (Builder.LazySimpDestinationMessageMatcher) matcher;
95-
return new MessageAuthorizationContext<>(message, path.extractPathVariables(message));
97+
if (matcher instanceof Builder.LazySimpDestinationPatternMessageMatcher pathMatcher) {
98+
return new MessageAuthorizationContext<>(message, pathMatcher.extractPathVariables(message));
9699
}
97100
return new MessageAuthorizationContext<>(message);
98101
}
@@ -112,8 +115,11 @@ public static final class Builder {
112115

113116
private final List<Entry<AuthorizationManager<MessageAuthorizationContext<?>>>> mappings = new ArrayList<>();
114117

118+
@Deprecated
115119
private Supplier<PathMatcher> pathMatcher = AntPathMatcher::new;
116120

121+
private boolean useHttpPathSeparator = true;
122+
117123
public Builder() {
118124
}
119125

@@ -132,11 +138,11 @@ public Builder.Constraint anyMessage() {
132138
* @return the Expression to associate
133139
*/
134140
public Builder.Constraint nullDestMatcher() {
135-
return matchers(SimpDestinationMessageMatcher.NULL_DESTINATION_MATCHER);
141+
return matchers(PathPatternMessageMatcher.NULL_DESTINATION_MATCHER);
136142
}
137143

138144
/**
139-
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances.
145+
* Maps a {@link List} of {@link SimpMessageTypeMatcher} instances.
140146
* @param typesToMatch the {@link SimpMessageType} instance to match on
141147
* @return the {@link Builder.Constraint} associated to the matchers.
142148
*/
@@ -156,35 +162,88 @@ public Builder.Constraint simpTypeMatchers(SimpMessageType... typesToMatch) {
156162
* @param patterns the patterns to create
157163
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
158164
* from.
165+
* @deprecated use {@link #destinationPathPatterns(String...)}
159166
*/
167+
@Deprecated
160168
public Builder.Constraint simpDestMatchers(String... patterns) {
161169
return simpDestMatchers(null, patterns);
162170
}
163171

172+
/**
173+
* Allows the creation of a security {@link Constraint} applying to messages whose
174+
* destinations match the provided {@code patterns}.
175+
* <p>
176+
* The matching of each pattern is performed by a
177+
* {@link PathPatternMessageMatcher} instance that matches irrespectively of
178+
* {@link SimpMessageType}. If no destination is found on the {@code Message},
179+
* then each {@code Matcher} returns false.
180+
* </p>
181+
* @param patterns the destination path patterns to which the security
182+
* {@code Constraint} will be applicable
183+
* @since 6.5
184+
*/
185+
public Builder.Constraint destinationPathPatterns(String... patterns) {
186+
return destinationPathPatterns(null, patterns);
187+
}
188+
164189
/**
165190
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances that
166191
* match on {@code SimpMessageType.MESSAGE}. If no destination is found on the
167192
* Message, then the Matcher returns false.
168193
* @param patterns the patterns to create
169194
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
170195
* from.
196+
* @deprecated use {@link #destinationPathPatterns(String...)}
171197
*/
198+
@Deprecated
172199
public Builder.Constraint simpMessageDestMatchers(String... patterns) {
173200
return simpDestMatchers(SimpMessageType.MESSAGE, patterns);
174201
}
175202

203+
/**
204+
* Allows the creation of a security {@link Constraint} applying to messages of
205+
* the type {@code SimpMessageType.MESSAGE} whose destinations match the provided
206+
* {@code patterns}.
207+
* <p>
208+
* The matching of each pattern is performed by a
209+
* {@link PathPatternMessageMatcher}. If no destination is found on the
210+
* {@code Message}, then each {@code Matcher} returns false.
211+
* @param patterns the patterns to create {@link PathPatternMessageMatcher} from.
212+
* @since 6.5
213+
*/
214+
public Builder.Constraint simpTypeMessageDestinationPatterns(String... patterns) {
215+
return destinationPathPatterns(SimpMessageType.MESSAGE, patterns);
216+
}
217+
176218
/**
177219
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances that
178220
* match on {@code SimpMessageType.SUBSCRIBE}. If no destination is found on the
179221
* Message, then the Matcher returns false.
180222
* @param patterns the patterns to create
181223
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
182224
* from.
225+
* @deprecated use {@link #simpTypeSubscribeDestinationPatterns(String...)}
183226
*/
227+
@Deprecated
184228
public Builder.Constraint simpSubscribeDestMatchers(String... patterns) {
185229
return simpDestMatchers(SimpMessageType.SUBSCRIBE, patterns);
186230
}
187231

232+
/**
233+
* Allows the creation of a security {@link Constraint} applying to messages of
234+
* the type {@code SimpMessageType.SUBSCRIBE} whose destinations match the
235+
* provided {@code patterns}.
236+
* <p>
237+
* The matching of each pattern is performed by a
238+
* {@link PathPatternMessageMatcher}. If no destination is found on the
239+
* {@code Message}, then each {@code Matcher} returns false.
240+
* @param patterns the patterns to create {@link PathPatternMessageMatcher} from.
241+
* @since 6.5
242+
*/
243+
public Builder.Constraint simpTypeSubscribeDestinationPatterns(String... patterns) {
244+
return destinationPathPatterns(SimpMessageType.SUBSCRIBE, patterns);
245+
}
246+
188247
/**
189248
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances. If no
190249
* destination is found on the Message, then the Matcher returns false.
@@ -195,7 +254,9 @@ public Builder.Constraint simpSubscribeDestMatchers(String... patterns) {
195254
* from.
196255
* @return the {@link Builder.Constraint} that is associated to the
197256
* {@link MessageMatcher}
257+
* @deprecated use {@link #destinationPathPatterns(String...)}
198258
*/
259+
@Deprecated
199260
private Builder.Constraint simpDestMatchers(SimpMessageType type, String... patterns) {
200261
List<MessageMatcher<?>> matchers = new ArrayList<>(patterns.length);
201262
for (String pattern : patterns) {
@@ -205,13 +266,51 @@ private Builder.Constraint simpDestMatchers(SimpMessageType type, String... patt
205266
return new Builder.Constraint(matchers);
206267
}
207268

269+
/**
270+
* Allows the creation of a security {@link Constraint} applying to messages of
271+
* the provided {@code type} whose destinations match the provided
272+
* {@code patterns}.
273+
* <p>
274+
* The matching of each pattern is performed by a
275+
* {@link PathPatternMessageMatcher}. If no destination is found on the
276+
* {@code Message}, then each {@code Matcher} returns false.
277+
* </p>
278+
* @param type the {@link SimpMessageType} to match on. If null, the
279+
* {@link SimpMessageType} is not considered for matching.
280+
* @param patterns the patterns to create {@link PathPatternMessageMatcher} from.
281+
* @return the {@link Builder.Constraint} that is associated to the
282+
* {@link MessageMatcher}s
283+
* @since 6.5
284+
*/
285+
private Builder.Constraint destinationPathPatterns(SimpMessageType type, String... patterns) {
286+
List<MessageMatcher<?>> matchers = new ArrayList<>(patterns.length);
287+
for (String pattern : patterns) {
288+
MessageMatcher<Object> matcher = new LazySimpDestinationPatternMessageMatcher(pattern, type,
289+
this.useHttpPathSeparator);
290+
matchers.add(matcher);
291+
}
292+
return new Builder.Constraint(matchers);
293+
}
294+
295+
/**
296+
* Instruct this builder to match message destinations using the separator
297+
* configured in
298+
* {@link org.springframework.http.server.PathContainer.Options#MESSAGE_ROUTE}
299+
*/
300+
public Builder messageRouteSeparator() {
301+
this.useHttpPathSeparator = false;
302+
return this;
303+
}
304+
208305
/**
209306
* The {@link PathMatcher} to be used with the
210307
* {@link Builder#simpDestMatchers(String...)}. The default is to use the default
211308
* constructor of {@link AntPathMatcher}.
212309
* @param pathMatcher the {@link PathMatcher} to use. Cannot be null.
213310
* @return the {@link Builder} for further customization.
311+
* @deprecated use {@link #messageRouteSeparator()} to alter the path separator
214312
*/
313+
@Deprecated
215314
public Builder simpDestPathMatcher(PathMatcher pathMatcher) {
216315
Assert.notNull(pathMatcher, "pathMatcher cannot be null");
217316
this.pathMatcher = () -> pathMatcher;
@@ -224,7 +323,9 @@ public Builder simpDestPathMatcher(PathMatcher pathMatcher) {
224323
* computation or lookup of the {@link PathMatcher}.
225324
* @param pathMatcher the {@link PathMatcher} to use. Cannot be null.
226325
* @return the {@link Builder} for further customization.
326+
* @deprecated use {@link #messageRouteSeparator()} to alter the path separator
227327
*/
328+
@Deprecated
228329
public Builder simpDestPathMatcher(Supplier<PathMatcher> pathMatcher) {
229330
Assert.notNull(pathMatcher, "pathMatcher cannot be null");
230331
this.pathMatcher = pathMatcher;
@@ -240,9 +341,7 @@ public Builder simpDestPathMatcher(Supplier<PathMatcher> pathMatcher) {
240341
*/
241342
public Builder.Constraint matchers(MessageMatcher<?>... matchers) {
242343
List<MessageMatcher<?>> builders = new ArrayList<>(matchers.length);
243-
for (MessageMatcher<?> matcher : matchers) {
244-
builders.add(matcher);
245-
}
344+
builders.addAll(Arrays.asList(matchers));
246345
return new Builder.Constraint(builders);
247346
}
248347

@@ -381,6 +480,7 @@ public Builder access(AuthorizationManager<MessageAuthorizationContext<?>> autho
381480

382481
}
383482

483+
@Deprecated
384484
private final class LazySimpDestinationMessageMatcher implements MessageMatcher<Object> {
385485

386486
private final Supplier<SimpDestinationMessageMatcher> delegate;
@@ -412,6 +512,40 @@ Map<String, String> extractPathVariables(Message<?> message) {
412512

413513
}
414514

515+
private static final class LazySimpDestinationPatternMessageMatcher implements MessageMatcher<Object> {
516+
517+
private final Supplier<PathPatternMessageMatcher> delegate;
518+
519+
private LazySimpDestinationPatternMessageMatcher(String pattern, SimpMessageType type,
520+
boolean useHttpPathSeparator) {
521+
this.delegate = SingletonSupplier.of(() -> {
522+
PathPatternParser dotSeparatedPathParser = new PathPatternParser();
523+
dotSeparatedPathParser.setPathOptions(PathContainer.Options.MESSAGE_ROUTE);
524+
PathPatternMessageMatcher.Builder builder = (useHttpPathSeparator)
525+
? PathPatternMessageMatcher.withDefaults()
526+
: PathPatternMessageMatcher.withPathPatternParser(dotSeparatedPathParser);
527+
if (type == null) {
528+
return builder.matcher(pattern);
529+
}
530+
if (SimpMessageType.MESSAGE == type || SimpMessageType.SUBSCRIBE == type) {
531+
return builder.matcher(pattern, type);
532+
}
533+
throw new IllegalStateException(type + " is not supported since it does not have a destination");
534+
});
535+
}
536+
537+
@Override
538+
public boolean matches(Message<?> message) {
539+
return this.delegate.get().matches(message);
540+
}
541+
542+
Map<String, String> extractPathVariables(Message<?> message) {
543+
MatchResult matchResult = this.delegate.get().matcher(message);
544+
return matchResult.getVariables();
545+
}
546+
547+
}
548+
415549
}
416550

417551
private static final class Entry<T> {
@@ -420,7 +554,7 @@ private static final class Entry<T> {
420554

421555
private final T entry;
422556

423-
Entry(MessageMatcher requestMatcher, T entry) {
557+
Entry(MessageMatcher<?> requestMatcher, T entry) {
424558
this.messageMatcher = requestMatcher;
425559
this.entry = entry;
426560
}

0 commit comments

Comments
 (0)