Skip to content

Commit 9e2b3d9

Browse files
committed
Create PathPatternMessageMatcher
Signed-off-by: Pat McCusker <[email protected]>
1 parent ed3c40f commit 9e2b3d9

File tree

7 files changed

+517
-26
lines changed

7 files changed

+517
-26
lines changed

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 145 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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;
@@ -34,12 +36,14 @@
3436
import org.springframework.security.authorization.SingleResultAuthorizationManager;
3537
import org.springframework.security.core.Authentication;
3638
import org.springframework.security.messaging.util.matcher.MessageMatcher;
39+
import org.springframework.security.messaging.util.matcher.PathPatternMessageMatcher;
3740
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
3841
import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher;
3942
import org.springframework.util.AntPathMatcher;
4043
import org.springframework.util.Assert;
4144
import org.springframework.util.PathMatcher;
4245
import org.springframework.util.function.SingletonSupplier;
46+
import org.springframework.web.util.pattern.PathPatternParser;
4347

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

@@ -88,12 +92,11 @@ private MessageAuthorizationContext<?> authorizationContext(MessageMatcher<?> ma
8892
if (!matcher.matches((Message) message)) {
8993
return null;
9094
}
91-
if (matcher instanceof SimpDestinationMessageMatcher simp) {
92-
return new MessageAuthorizationContext<>(message, simp.extractPathVariables(message));
95+
if (matcher instanceof Builder.LazySimpDestinationMessageMatcher pathMatcher) {
96+
return new MessageAuthorizationContext<>(message, pathMatcher.extractPathVariables(message));
9397
}
94-
if (matcher instanceof Builder.LazySimpDestinationMessageMatcher) {
95-
Builder.LazySimpDestinationMessageMatcher path = (Builder.LazySimpDestinationMessageMatcher) matcher;
96-
return new MessageAuthorizationContext<>(message, path.extractPathVariables(message));
98+
if (matcher instanceof Builder.LazySimpDestinationPatternMessageMatcher pathMatcher) {
99+
return new MessageAuthorizationContext<>(message, pathMatcher.extractPathVariables(message));
97100
}
98101
return new MessageAuthorizationContext<>(message);
99102
}
@@ -113,8 +116,11 @@ public static final class Builder {
113116

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

119+
@Deprecated
116120
private Supplier<PathMatcher> pathMatcher = AntPathMatcher::new;
117121

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

@@ -133,11 +139,11 @@ public Builder.Constraint anyMessage() {
133139
* @return the Expression to associate
134140
*/
135141
public Builder.Constraint nullDestMatcher() {
136-
return matchers(SimpDestinationMessageMatcher.NULL_DESTINATION_MATCHER);
142+
return matchers(PathPatternMessageMatcher.NULL_DESTINATION_MATCHER);
137143
}
138144

139145
/**
140-
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances.
146+
* Maps a {@link List} of {@link SimpMessageTypeMatcher} instances.
141147
* @param typesToMatch the {@link SimpMessageType} instance to match on
142148
* @return the {@link Builder.Constraint} associated to the matchers.
143149
*/
@@ -157,35 +163,88 @@ public Builder.Constraint simpTypeMatchers(SimpMessageType... typesToMatch) {
157163
* @param patterns the patterns to create
158164
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
159165
* from.
166+
* @deprecated use {@link #destinationPathPatterns(String...)}
160167
*/
168+
@Deprecated
161169
public Builder.Constraint simpDestMatchers(String... patterns) {
162170
return simpDestMatchers(null, patterns);
163171
}
164172

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

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

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

270+
/**
271+
* Allows the creation of a security {@link Constraint} applying to messages of
272+
* the provided {@code type} whose destinations match the provided
273+
* {@code patterns}.
274+
* <p>
275+
* The matching of each pattern is performed by a
276+
* {@link PathPatternMessageMatcher}. If no destination is found on the
277+
* {@code Message}, then each {@code Matcher} returns false.
278+
* </p>
279+
* @param type the {@link SimpMessageType} to match on. If null, the
280+
* {@link SimpMessageType} is not considered for matching.
281+
* @param patterns the patterns to create {@link PathPatternMessageMatcher} 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+
209306
/**
210307
* The {@link PathMatcher} to be used with the
211308
* {@link Builder#simpDestMatchers(String...)}. The default is to use the default
212309
* constructor of {@link AntPathMatcher}.
213310
* @param pathMatcher the {@link PathMatcher} to use. Cannot be null.
214311
* @return the {@link Builder} for further customization.
312+
* @deprecated use {@link #messageRouteSeparator()} to alter the path separator
215313
*/
314+
@Deprecated
216315
public Builder simpDestPathMatcher(PathMatcher pathMatcher) {
217316
Assert.notNull(pathMatcher, "pathMatcher cannot be null");
218317
this.pathMatcher = () -> pathMatcher;
@@ -225,7 +324,9 @@ public Builder simpDestPathMatcher(PathMatcher pathMatcher) {
225324
* computation or lookup of the {@link PathMatcher}.
226325
* @param pathMatcher the {@link PathMatcher} to use. Cannot be null.
227326
* @return the {@link Builder} for further customization.
327+
* @deprecated use {@link #messageRouteSeparator()} to alter the path separator
228328
*/
329+
@Deprecated
229330
public Builder simpDestPathMatcher(Supplier<PathMatcher> pathMatcher) {
230331
Assert.notNull(pathMatcher, "pathMatcher cannot be null");
231332
this.pathMatcher = pathMatcher;
@@ -241,9 +342,7 @@ public Builder simpDestPathMatcher(Supplier<PathMatcher> pathMatcher) {
241342
*/
242343
public Builder.Constraint matchers(MessageMatcher<?>... matchers) {
243344
List<MessageMatcher<?>> builders = new ArrayList<>(matchers.length);
244-
for (MessageMatcher<?> matcher : matchers) {
245-
builders.add(matcher);
246-
}
345+
builders.addAll(Arrays.asList(matchers));
247346
return new Builder.Constraint(builders);
248347
}
249348

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

383482
}
384483

484+
@Deprecated
385485
private final class LazySimpDestinationMessageMatcher implements MessageMatcher<Object> {
386486

387487
private final Supplier<SimpDestinationMessageMatcher> delegate;
@@ -413,6 +513,40 @@ Map<String, String> extractPathVariables(Message<?> message) {
413513

414514
}
415515

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

418552
private static final class Entry<T> {
@@ -421,7 +555,7 @@ private static final class Entry<T> {
421555

422556
private final T entry;
423557

424-
Entry(MessageMatcher requestMatcher, T entry) {
558+
Entry(MessageMatcher<?> requestMatcher, T entry) {
425559
this.messageMatcher = requestMatcher;
426560
this.entry = entry;
427561
}

0 commit comments

Comments
 (0)