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.
1717package org .springframework .security .messaging .access .intercept ;
1818
1919import java .util .ArrayList ;
20+ import java .util .Arrays ;
2021import java .util .List ;
2122import java .util .Map ;
2223import java .util .function .Supplier ;
2526import org .apache .commons .logging .LogFactory ;
2627
2728import org .springframework .core .log .LogMessage ;
29+ import org .springframework .http .server .PathContainer ;
2830import org .springframework .messaging .Message ;
2931import org .springframework .messaging .simp .SimpMessageType ;
3032import org .springframework .security .authorization .AuthenticatedAuthorizationManager ;
3335import org .springframework .security .authorization .AuthorizationManager ;
3436import org .springframework .security .core .Authentication ;
3537import org .springframework .security .messaging .util .matcher .MessageMatcher ;
38+ import org .springframework .security .messaging .util .matcher .PathPatternMessageMatcher ;
3639import org .springframework .security .messaging .util .matcher .SimpDestinationMessageMatcher ;
3740import org .springframework .security .messaging .util .matcher .SimpMessageTypeMatcher ;
3841import org .springframework .util .AntPathMatcher ;
3942import org .springframework .util .Assert ;
4043import org .springframework .util .PathMatcher ;
4144import org .springframework .util .function .SingletonSupplier ;
45+ import org .springframework .web .util .pattern .PathPatternParser ;
4246
4347public 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