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 ;
3233import org .springframework .security .authorization .AuthorizationDecision ;
3334import org .springframework .security .authorization .AuthorizationManager ;
3435import org .springframework .security .core .Authentication ;
36+ import org .springframework .security .messaging .util .matcher .DestinationPathPatternMessageMatcher ;
3537import org .springframework .security .messaging .util .matcher .MessageMatcher ;
3638import org .springframework .security .messaging .util .matcher .SimpDestinationMessageMatcher ;
3739import 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