Skip to content

Commit de8c0ce

Browse files
committed
Add DestinationPathPatternMessageMatcher
Closes gh-16500 Signed-off-by: Pat McCusker <[email protected]>
1 parent 7d301f8 commit de8c0ce

File tree

8 files changed

+522
-36
lines changed

8 files changed

+522
-36
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.

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

+1-1
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.

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

+144-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;
@@ -32,6 +33,7 @@
3233
import org.springframework.security.authorization.AuthorizationDecision;
3334
import org.springframework.security.authorization.AuthorizationManager;
3435
import org.springframework.security.core.Authentication;
36+
import org.springframework.security.messaging.util.matcher.DestinationPathPatternMessageMatcher;
3537
import org.springframework.security.messaging.util.matcher.MessageMatcher;
3638
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
3739
import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher;
@@ -87,12 +89,11 @@ private MessageAuthorizationContext<?> authorizationContext(MessageMatcher<?> ma
8789
if (!matcher.matches((Message) message)) {
8890
return null;
8991
}
90-
if (matcher instanceof SimpDestinationMessageMatcher simp) {
91-
return new MessageAuthorizationContext<>(message, simp.extractPathVariables(message));
92+
if (matcher instanceof Builder.LazySimpDestinationMessageMatcher pathMatcher) {
93+
return new MessageAuthorizationContext<>(message, pathMatcher.extractPathVariables(message));
9294
}
93-
if (matcher instanceof Builder.LazySimpDestinationMessageMatcher) {
94-
Builder.LazySimpDestinationMessageMatcher path = (Builder.LazySimpDestinationMessageMatcher) matcher;
95-
return new MessageAuthorizationContext<>(message, path.extractPathVariables(message));
95+
if (matcher instanceof Builder.LazySimpDestinationPatternMessageMatcher pathMatcher) {
96+
return new MessageAuthorizationContext<>(message, pathMatcher.extractPathVariables(message));
9697
}
9798
return new MessageAuthorizationContext<>(message);
9899
}
@@ -112,8 +113,11 @@ public static final class Builder {
112113

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

116+
@Deprecated
115117
private Supplier<PathMatcher> pathMatcher = AntPathMatcher::new;
116118

119+
private boolean useHttpPathSeparator = true;
120+
117121
public Builder() {
118122
}
119123

@@ -132,11 +136,11 @@ public Builder.Constraint anyMessage() {
132136
* @return the Expression to associate
133137
*/
134138
public Builder.Constraint nullDestMatcher() {
135-
return matchers(SimpDestinationMessageMatcher.NULL_DESTINATION_MATCHER);
139+
return matchers(DestinationPathPatternMessageMatcher.NULL_DESTINATION_MATCHER);
136140
}
137141

138142
/**
139-
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances.
143+
* Maps a {@link List} of {@link SimpMessageTypeMatcher} instances.
140144
* @param typesToMatch the {@link SimpMessageType} instance to match on
141145
* @return the {@link Builder.Constraint} associated to the matchers.
142146
*/
@@ -156,35 +160,90 @@ public Builder.Constraint simpTypeMatchers(SimpMessageType... typesToMatch) {
156160
* @param patterns the patterns to create
157161
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
158162
* from.
163+
* @deprecated use {@link #destinationPathPatterns(String...)}
159164
*/
165+
@Deprecated
160166
public Builder.Constraint simpDestMatchers(String... patterns) {
161167
return simpDestMatchers(null, patterns);
162168
}
163169

170+
/**
171+
* Allows the creation of a security {@link Constraint} applying to messages whose
172+
* destinations match the provided {@code patterns}.
173+
* <p>
174+
* The matching of each pattern is performed by a
175+
* {@link DestinationPathPatternMessageMatcher} instance that matches
176+
* irrespectively of {@link SimpMessageType}. If no destination is found on the
177+
* {@code Message}, then each {@code Matcher} returns false.
178+
* </p>
179+
* @param patterns the destination path patterns to which the security
180+
* {@code Constraint} will be applicable
181+
* @since 6.5
182+
*/
183+
public Builder.Constraint destinationPathPatterns(String... patterns) {
184+
return destinationPathPatterns(null, patterns);
185+
}
186+
164187
/**
165188
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances that
166189
* match on {@code SimpMessageType.MESSAGE}. If no destination is found on the
167190
* Message, then the Matcher returns false.
168191
* @param patterns the patterns to create
169192
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
170193
* from.
194+
* @deprecated use {@link #destinationPathPatterns(String...)}
171195
*/
196+
@Deprecated
172197
public Builder.Constraint simpMessageDestMatchers(String... patterns) {
173198
return simpDestMatchers(SimpMessageType.MESSAGE, patterns);
174199
}
175200

201+
/**
202+
* Allows the creation of a security {@link Constraint} applying to messages of
203+
* the type {@code SimpMessageType.MESSAGE} whose destinations match the provided
204+
* {@code patterns}.
205+
* <p>
206+
* The matching of each pattern is performed by a
207+
* {@link DestinationPathPatternMessageMatcher}. If no destination is found on the
208+
* {@code Message}, then each {@code Matcher} returns false.
209+
* @param patterns the patterns to create
210+
* {@link DestinationPathPatternMessageMatcher} from.
211+
* @since 6.5
212+
*/
213+
public Builder.Constraint simpTypeMessageDestinationPatterns(String... patterns) {
214+
return destinationPathPatterns(SimpMessageType.MESSAGE, patterns);
215+
}
216+
176217
/**
177218
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances that
178219
* match on {@code SimpMessageType.SUBSCRIBE}. If no destination is found on the
179220
* Message, then the Matcher returns false.
180221
* @param patterns the patterns to create
181222
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
182223
* from.
224+
* @deprecated use {@link #simpTypeSubscribeDestinationPatterns(String...)}
183225
*/
226+
@Deprecated
184227
public Builder.Constraint simpSubscribeDestMatchers(String... patterns) {
185228
return simpDestMatchers(SimpMessageType.SUBSCRIBE, patterns);
186229
}
187230

231+
/**
232+
* Allows the creation of a security {@link Constraint} applying to messages of
233+
* the type {@code SimpMessageType.SUBSCRIBE} whose destinations match the
234+
* provided {@code patterns}.
235+
* <p>
236+
* The matching of each pattern is performed by a
237+
* {@link DestinationPathPatternMessageMatcher}. If no destination is found on the
238+
* {@code Message}, then each {@code Matcher} returns false.
239+
* @param patterns the patterns to create
240+
* {@link DestinationPathPatternMessageMatcher} 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,52 @@ 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 DestinationPathPatternMessageMatcher}. 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
281+
* {@link DestinationPathPatternMessageMatcher} from.
282+
* @return the {@link Builder.Constraint} that is associated to the
283+
* {@link MessageMatcher}s
284+
* @since 6.5
285+
*/
286+
private Builder.Constraint destinationPathPatterns(SimpMessageType type, String... patterns) {
287+
List<MessageMatcher<?>> matchers = new ArrayList<>(patterns.length);
288+
for (String pattern : patterns) {
289+
MessageMatcher<Object> matcher = new LazySimpDestinationPatternMessageMatcher(pattern, type,
290+
this.useHttpPathSeparator);
291+
matchers.add(matcher);
292+
}
293+
return new Builder.Constraint(matchers);
294+
}
295+
296+
/**
297+
* Instruct this builder to match message destinations using the separator
298+
* configured in
299+
* {@link org.springframework.http.server.PathContainer.Options#MESSAGE_ROUTE}
300+
*/
301+
public Builder messageRouteSeparator() {
302+
this.useHttpPathSeparator = false;
303+
return this;
304+
}
305+
208306
/**
209307
* The {@link PathMatcher} to be used with the
210308
* {@link Builder#simpDestMatchers(String...)}. The default is to use the default
211309
* constructor of {@link AntPathMatcher}.
212310
* @param pathMatcher the {@link PathMatcher} to use. Cannot be null.
213311
* @return the {@link Builder} for further customization.
312+
* @deprecated use {@link #messageRouteSeparator()} to alter the path separator
214313
*/
314+
@Deprecated
215315
public Builder simpDestPathMatcher(PathMatcher pathMatcher) {
216316
Assert.notNull(pathMatcher, "pathMatcher cannot be null");
217317
this.pathMatcher = () -> pathMatcher;
@@ -224,7 +324,9 @@ public Builder simpDestPathMatcher(PathMatcher pathMatcher) {
224324
* computation or lookup of the {@link PathMatcher}.
225325
* @param pathMatcher the {@link PathMatcher} to use. Cannot be null.
226326
* @return the {@link Builder} for further customization.
327+
* @deprecated use {@link #messageRouteSeparator()} to alter the path separator
227328
*/
329+
@Deprecated
228330
public Builder simpDestPathMatcher(Supplier<PathMatcher> pathMatcher) {
229331
Assert.notNull(pathMatcher, "pathMatcher cannot be null");
230332
this.pathMatcher = pathMatcher;
@@ -240,9 +342,7 @@ public Builder simpDestPathMatcher(Supplier<PathMatcher> pathMatcher) {
240342
*/
241343
public Builder.Constraint matchers(MessageMatcher<?>... matchers) {
242344
List<MessageMatcher<?>> builders = new ArrayList<>(matchers.length);
243-
for (MessageMatcher<?> matcher : matchers) {
244-
builders.add(matcher);
245-
}
345+
builders.addAll(Arrays.asList(matchers));
246346
return new Builder.Constraint(builders);
247347
}
248348

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

382482
}
383483

484+
@Deprecated
384485
private final class LazySimpDestinationMessageMatcher implements MessageMatcher<Object> {
385486

386487
private final Supplier<SimpDestinationMessageMatcher> delegate;
@@ -412,6 +513,37 @@ Map<String, String> extractPathVariables(Message<?> message) {
412513

413514
}
414515

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

417549
private static final class Entry<T> {
@@ -420,7 +552,7 @@ private static final class Entry<T> {
420552

421553
private final T entry;
422554

423-
Entry(MessageMatcher requestMatcher, T entry) {
555+
Entry(MessageMatcher<?> requestMatcher, T entry) {
424556
this.messageMatcher = requestMatcher;
425557
this.entry = entry;
426558
}

0 commit comments

Comments
 (0)