Skip to content

Commit 7cc8706

Browse files
pat-mccuskerjzheaux
authored andcommitted
Add PathPatternMessageMatcher
Closes spring-projectsgh-16500 Signed-off-by: Pat McCusker <[email protected]>
1 parent 44665c1 commit 7cc8706

File tree

10 files changed

+597
-52
lines changed

10 files changed

+597
-52
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/socket/MessageMatcherAuthorizationManagerConfiguration.java

Lines changed: 3 additions & 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.
@@ -21,6 +21,7 @@
2121
import org.springframework.context.annotation.Scope;
2222
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
2323
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
24+
import org.springframework.security.messaging.util.matcher.MessageMatcherFactory;
2425
import org.springframework.util.AntPathMatcher;
2526

2627
final class MessageMatcherAuthorizationManagerConfiguration {
@@ -29,6 +30,7 @@ final class MessageMatcherAuthorizationManagerConfiguration {
2930
@Scope("prototype")
3031
MessageMatcherDelegatingAuthorizationManager.Builder messageAuthorizationManagerBuilder(
3132
ApplicationContext context) {
33+
MessageMatcherFactory.setApplicationContext(context);
3234
return MessageMatcherDelegatingAuthorizationManager.builder()
3335
.simpDestPathMatcher(
3436
() -> (context.getBeanNamesForType(SimpAnnotationMethodMessageHandler.class).length > 0)

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

Lines changed: 48 additions & 36 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;
@@ -34,10 +35,13 @@
3435
import org.springframework.security.authorization.SingleResultAuthorizationManager;
3536
import org.springframework.security.core.Authentication;
3637
import org.springframework.security.messaging.util.matcher.MessageMatcher;
38+
import org.springframework.security.messaging.util.matcher.MessageMatcherFactory;
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;
44+
import org.springframework.util.CollectionUtils;
4145
import org.springframework.util.PathMatcher;
4246
import org.springframework.util.function.SingletonSupplier;
4347

@@ -85,15 +89,17 @@ public AuthorizationDecision check(Supplier<Authentication> authentication, Mess
8589
}
8690

8791
private MessageAuthorizationContext<?> authorizationContext(MessageMatcher<?> matcher, Message<?> message) {
88-
if (!matcher.matches((Message) message)) {
92+
MessageMatcher.MatchResult matchResult = matcher.matcher((Message) message);
93+
if (!matchResult.isMatch()) {
8994
return null;
9095
}
91-
if (matcher instanceof SimpDestinationMessageMatcher simp) {
92-
return new MessageAuthorizationContext<>(message, simp.extractPathVariables(message));
96+
97+
if (!CollectionUtils.isEmpty(matchResult.getVariables())) {
98+
return new MessageAuthorizationContext<>(message, matchResult.getVariables());
9399
}
94-
if (matcher instanceof Builder.LazySimpDestinationMessageMatcher) {
95-
Builder.LazySimpDestinationMessageMatcher path = (Builder.LazySimpDestinationMessageMatcher) matcher;
96-
return new MessageAuthorizationContext<>(message, path.extractPathVariables(message));
100+
101+
if (matcher instanceof Builder.LazySimpDestinationMessageMatcher pathMatcher) {
102+
return new MessageAuthorizationContext<>(message, pathMatcher.extractPathVariables(message));
97103
}
98104
return new MessageAuthorizationContext<>(message);
99105
}
@@ -113,6 +119,7 @@ public static final class Builder {
113119

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

122+
@Deprecated
116123
private Supplier<PathMatcher> pathMatcher = AntPathMatcher::new;
117124

118125
public Builder() {
@@ -133,11 +140,11 @@ public Builder.Constraint anyMessage() {
133140
* @return the Expression to associate
134141
*/
135142
public Builder.Constraint nullDestMatcher() {
136-
return matchers(SimpDestinationMessageMatcher.NULL_DESTINATION_MATCHER);
143+
return matchers(PathPatternMessageMatcher.NULL_DESTINATION_MATCHER);
137144
}
138145

139146
/**
140-
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances.
147+
* Maps a {@link List} of {@link SimpMessageTypeMatcher} instances.
141148
* @param typesToMatch the {@link SimpMessageType} instance to match on
142149
* @return the {@link Builder.Constraint} associated to the matchers.
143150
*/
@@ -151,56 +158,58 @@ public Builder.Constraint simpTypeMatchers(SimpMessageType... typesToMatch) {
151158
}
152159

153160
/**
154-
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances without
155-
* regard to the {@link SimpMessageType}. If no destination is found on the
156-
* Message, then the Matcher returns false.
157-
* @param patterns the patterns to create
158-
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
159-
* from.
161+
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} (or
162+
* {@link PathPatternMessageMatcher} if the application has configured a
163+
* {@link org.springframework.security.messaging.util.matcher.PathPatternMessageMatcherBuilderFactoryBean})
164+
* instances without regard to the {@link SimpMessageType}. If no destination is
165+
* found on the Message, then the Matcher returns false.
166+
* @param patterns the patterns to create {@code MessageMatcher}s from.
160167
*/
161168
public Builder.Constraint simpDestMatchers(String... patterns) {
162169
return simpDestMatchers(null, patterns);
163170
}
164171

165172
/**
166-
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances that
167-
* match on {@code SimpMessageType.MESSAGE}. If no destination is found on the
168-
* Message, then the Matcher returns false.
169-
* @param patterns the patterns to create
170-
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
171-
* from.
173+
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} (or
174+
* {@link PathPatternMessageMatcher} if the application has configured a
175+
* {@link org.springframework.security.messaging.util.matcher.PathPatternMessageMatcherBuilderFactoryBean})
176+
* instances that match on {@code SimpMessageType.MESSAGE}. If no destination is
177+
* found on the Message, then the Matcher returns false.
178+
* @param patterns the patterns to create {@code MessageMatcher}s from.
172179
*/
173180
public Builder.Constraint simpMessageDestMatchers(String... patterns) {
174181
return simpDestMatchers(SimpMessageType.MESSAGE, patterns);
175182
}
176183

177184
/**
178-
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances that
179-
* match on {@code SimpMessageType.SUBSCRIBE}. If no destination is found on the
180-
* Message, then the Matcher returns false.
181-
* @param patterns the patterns to create
182-
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
183-
* from.
185+
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} (or
186+
* {@link PathPatternMessageMatcher} if the application has configured a
187+
* {@link org.springframework.security.messaging.util.matcher.PathPatternMessageMatcherBuilderFactoryBean})
188+
* instances that match on {@code SimpMessageType.SUBSCRIBE}. If no destination is
189+
* found on the Message, then the Matcher returns false.
190+
* @param patterns the patterns to create {@code MessageMatcher}s from.
184191
*/
185192
public Builder.Constraint simpSubscribeDestMatchers(String... patterns) {
186193
return simpDestMatchers(SimpMessageType.SUBSCRIBE, patterns);
187194
}
188195

189196
/**
190-
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances. If no
191-
* destination is found on the Message, then the Matcher returns false.
197+
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances, or
198+
* {@link PathPatternMessageMatcher} if the application has configured a
199+
* {@link org.springframework.security.messaging.util.matcher.PathPatternMessageMatcherBuilderFactoryBean}.
200+
* If no destination is found on the Message, then the Matcher returns false.
192201
* @param type the {@link SimpMessageType} to match on. If null, the
193202
* {@link SimpMessageType} is not considered for matching.
194-
* @param patterns the patterns to create
195-
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
196-
* from.
203+
* @param patterns the patterns to create {@code MessageMatcher}s from.
197204
* @return the {@link Builder.Constraint} that is associated to the
198205
* {@link MessageMatcher}
199206
*/
200207
private Builder.Constraint simpDestMatchers(SimpMessageType type, String... patterns) {
201208
List<MessageMatcher<?>> matchers = new ArrayList<>(patterns.length);
202209
for (String pattern : patterns) {
203-
MessageMatcher<Object> matcher = new LazySimpDestinationMessageMatcher(pattern, type);
210+
MessageMatcher<Object> matcher = MessageMatcherFactory.usesPathPatterns()
211+
? MessageMatcherFactory.matcher(pattern, type)
212+
: new LazySimpDestinationMessageMatcher(pattern, type);
204213
matchers.add(matcher);
205214
}
206215
return new Builder.Constraint(matchers);
@@ -212,7 +221,9 @@ private Builder.Constraint simpDestMatchers(SimpMessageType type, String... patt
212221
* constructor of {@link AntPathMatcher}.
213222
* @param pathMatcher the {@link PathMatcher} to use. Cannot be null.
214223
* @return the {@link Builder} for further customization.
224+
* @deprecated
215225
*/
226+
@Deprecated
216227
public Builder simpDestPathMatcher(PathMatcher pathMatcher) {
217228
Assert.notNull(pathMatcher, "pathMatcher cannot be null");
218229
this.pathMatcher = () -> pathMatcher;
@@ -225,7 +236,9 @@ public Builder simpDestPathMatcher(PathMatcher pathMatcher) {
225236
* computation or lookup of the {@link PathMatcher}.
226237
* @param pathMatcher the {@link PathMatcher} to use. Cannot be null.
227238
* @return the {@link Builder} for further customization.
239+
* @deprecated
228240
*/
241+
@Deprecated
229242
public Builder simpDestPathMatcher(Supplier<PathMatcher> pathMatcher) {
230243
Assert.notNull(pathMatcher, "pathMatcher cannot be null");
231244
this.pathMatcher = pathMatcher;
@@ -241,9 +254,7 @@ public Builder simpDestPathMatcher(Supplier<PathMatcher> pathMatcher) {
241254
*/
242255
public Builder.Constraint matchers(MessageMatcher<?>... matchers) {
243256
List<MessageMatcher<?>> builders = new ArrayList<>(matchers.length);
244-
for (MessageMatcher<?> matcher : matchers) {
245-
builders.add(matcher);
246-
}
257+
builders.addAll(Arrays.asList(matchers));
247258
return new Builder.Constraint(builders);
248259
}
249260

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

383394
}
384395

396+
@Deprecated
385397
private final class LazySimpDestinationMessageMatcher implements MessageMatcher<Object> {
386398

387399
private final Supplier<SimpDestinationMessageMatcher> delegate;
@@ -421,7 +433,7 @@ private static final class Entry<T> {
421433

422434
private final T entry;
423435

424-
Entry(MessageMatcher requestMatcher, T entry) {
436+
Entry(MessageMatcher<?> requestMatcher, T entry) {
425437
this.messageMatcher = requestMatcher;
426438
this.entry = entry;
427439
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.messaging.util.matcher;
18+
19+
import org.springframework.context.ApplicationContext;
20+
import org.springframework.messaging.simp.SimpMessageType;
21+
22+
@Deprecated(forRemoval = true)
23+
public final class MessageMatcherFactory {
24+
25+
private static PathPatternMessageMatcher.Builder builder;
26+
27+
public static void setApplicationContext(ApplicationContext context) {
28+
builder = context.getBeanProvider(PathPatternMessageMatcher.Builder.class).getIfUnique();
29+
}
30+
31+
public static boolean usesPathPatterns() {
32+
return builder != null;
33+
}
34+
35+
public static MessageMatcher<?> matcher(String destination) {
36+
return builder.matcher(destination);
37+
}
38+
39+
public static MessageMatcher<Object> matcher(String destination, SimpMessageType type) {
40+
return (type != null) ? builder.matcher(destination, type) : builder.matcher(destination);
41+
}
42+
43+
private MessageMatcherFactory() {
44+
}
45+
46+
}

0 commit comments

Comments
 (0)