Skip to content

Commit d32d0c4

Browse files
committed
Refactor PathPatternMessageMatcher
Signed-off-by: Pat McCusker <[email protected]>
1 parent 51b701d commit d32d0c4

File tree

6 files changed

+95
-97
lines changed

6 files changed

+95
-97
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2025 the original author or authors.
2+
* Copyright 2002-2024 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: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,23 @@
2626
import org.apache.commons.logging.LogFactory;
2727

2828
import org.springframework.core.log.LogMessage;
29+
import org.springframework.http.server.PathContainer;
2930
import org.springframework.messaging.Message;
3031
import org.springframework.messaging.simp.SimpMessageType;
3132
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
3233
import org.springframework.security.authorization.AuthorityAuthorizationManager;
3334
import org.springframework.security.authorization.AuthorizationDecision;
3435
import org.springframework.security.authorization.AuthorizationManager;
3536
import org.springframework.security.core.Authentication;
36-
import org.springframework.security.messaging.util.matcher.DestinationPathPatternMessageMatcher;
3737
import org.springframework.security.messaging.util.matcher.MessageMatcher;
38+
import org.springframework.security.messaging.util.matcher.PathPatternMessageMatcher;
3839
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
3940
import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher;
4041
import org.springframework.util.AntPathMatcher;
4142
import org.springframework.util.Assert;
4243
import org.springframework.util.PathMatcher;
4344
import org.springframework.util.function.SingletonSupplier;
45+
import org.springframework.web.util.pattern.PathPatternParser;
4446

4547
public final class MessageMatcherDelegatingAuthorizationManager implements AuthorizationManager<Message<?>> {
4648

@@ -136,7 +138,7 @@ public Builder.Constraint anyMessage() {
136138
* @return the Expression to associate
137139
*/
138140
public Builder.Constraint nullDestMatcher() {
139-
return matchers(DestinationPathPatternMessageMatcher.NULL_DESTINATION_MATCHER);
141+
return matchers(PathPatternMessageMatcher.NULL_DESTINATION_MATCHER);
140142
}
141143

142144
/**
@@ -172,9 +174,9 @@ public Builder.Constraint simpDestMatchers(String... patterns) {
172174
* destinations match the provided {@code patterns}.
173175
* <p>
174176
* 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.
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.
178180
* </p>
179181
* @param patterns the destination path patterns to which the security
180182
* {@code Constraint} will be applicable
@@ -204,10 +206,9 @@ public Builder.Constraint simpMessageDestMatchers(String... patterns) {
204206
* {@code patterns}.
205207
* <p>
206208
* The matching of each pattern is performed by a
207-
* {@link DestinationPathPatternMessageMatcher}. If no destination is found on the
209+
* {@link PathPatternMessageMatcher}. If no destination is found on the
208210
* {@code Message}, then each {@code Matcher} returns false.
209-
* @param patterns the patterns to create
210-
* {@link DestinationPathPatternMessageMatcher} from.
211+
* @param patterns the patterns to create {@link PathPatternMessageMatcher} from.
211212
* @since 6.5
212213
*/
213214
public Builder.Constraint simpTypeMessageDestinationPatterns(String... patterns) {
@@ -234,10 +235,9 @@ public Builder.Constraint simpSubscribeDestMatchers(String... patterns) {
234235
* provided {@code patterns}.
235236
* <p>
236237
* The matching of each pattern is performed by a
237-
* {@link DestinationPathPatternMessageMatcher}. If no destination is found on the
238+
* {@link PathPatternMessageMatcher}. If no destination is found on the
238239
* {@code Message}, then each {@code Matcher} returns false.
239-
* @param patterns the patterns to create
240-
* {@link DestinationPathPatternMessageMatcher} from.
240+
* @param patterns the patterns to create {@link PathPatternMessageMatcher} from.
241241
* @since 6.5
242242
*/
243243
public Builder.Constraint simpTypeSubscribeDestinationPatterns(String... patterns) {
@@ -272,13 +272,12 @@ private Builder.Constraint simpDestMatchers(SimpMessageType type, String... patt
272272
* {@code patterns}.
273273
* <p>
274274
* The matching of each pattern is performed by a
275-
* {@link DestinationPathPatternMessageMatcher}. If no destination is found on the
275+
* {@link PathPatternMessageMatcher}. If no destination is found on the
276276
* {@code Message}, then each {@code Matcher} returns false.
277277
* </p>
278278
* @param type the {@link SimpMessageType} to match on. If null, the
279279
* {@link SimpMessageType} is not considered for matching.
280-
* @param patterns the patterns to create
281-
* {@link DestinationPathPatternMessageMatcher} from.
280+
* @param patterns the patterns to create {@link PathPatternMessageMatcher} from.
282281
* @return the {@link Builder.Constraint} that is associated to the
283282
* {@link MessageMatcher}s
284283
* @since 6.5
@@ -515,19 +514,21 @@ Map<String, String> extractPathVariables(Message<?> message) {
515514

516515
private static final class LazySimpDestinationPatternMessageMatcher implements MessageMatcher<Object> {
517516

518-
private final Supplier<DestinationPathPatternMessageMatcher> delegate;
517+
private final Supplier<PathPatternMessageMatcher> delegate;
519518

520519
private LazySimpDestinationPatternMessageMatcher(String pattern, SimpMessageType type,
521520
boolean useHttpPathSeparator) {
522521
this.delegate = SingletonSupplier.of(() -> {
523-
DestinationPathPatternMessageMatcher.Builder builder = (useHttpPathSeparator)
524-
? DestinationPathPatternMessageMatcher.withDefaults()
525-
: DestinationPathPatternMessageMatcher.messageRoute();
522+
PathPatternParser dotSeparatedPathParser = new PathPatternParser();
523+
dotSeparatedPathParser.setPathOptions(PathContainer.Options.MESSAGE_ROUTE);
524+
PathPatternMessageMatcher.Builder builder = (useHttpPathSeparator)
525+
? PathPatternMessageMatcher.withDefaults()
526+
: PathPatternMessageMatcher.withPathPatternParser(dotSeparatedPathParser);
526527
if (type == null) {
527528
return builder.matcher(pattern);
528529
}
529530
if (SimpMessageType.MESSAGE == type || SimpMessageType.SUBSCRIBE == type) {
530-
return builder.messageType(type).matcher(pattern);
531+
return builder.matcher(pattern, type);
531532
}
532533
throw new IllegalStateException(type + " is not supported since it does not have a destination");
533534
});
Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,61 +19,55 @@
1919
import java.util.Collections;
2020
import java.util.Map;
2121

22+
import org.springframework.http.server.PathContainer;
2223
import org.springframework.messaging.Message;
2324
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
2425
import org.springframework.messaging.simp.SimpMessageType;
2526
import org.springframework.util.Assert;
26-
import org.springframework.util.RouteMatcher;
27+
import org.springframework.web.util.pattern.PathPattern;
2728
import org.springframework.web.util.pattern.PathPatternParser;
28-
import org.springframework.web.util.pattern.PathPatternRouteMatcher;
2929

3030
/**
31-
* Match {@link Message}s based on the message destination pattern, delegating the
32-
* matching to a {@link PathPatternRouteMatcher}. There is also support for optionally
33-
* matching on a specified {@link SimpMessageType}.
31+
* Match {@link Message}s based on the message destination pattern using a
32+
* {@link PathPattern}. There is also support for optionally matching on a specified
33+
* {@link SimpMessageType}.
3434
*
3535
* @author Pat McCusker
3636
* @since 6.5
3737
*/
38-
public final class DestinationPathPatternMessageMatcher implements MessageMatcher<Object> {
38+
public final class PathPatternMessageMatcher implements MessageMatcher<Object> {
3939

4040
public static final MessageMatcher<Object> NULL_DESTINATION_MATCHER = (message) -> getDestination(message) == null;
4141

42-
private static final PathPatternRouteMatcher SLASH_SEPARATED_ROUTE_MATCHER = new PathPatternRouteMatcher(
43-
PathPatternParser.defaultInstance);
42+
private final PathPattern pattern;
4443

45-
private static final PathPatternRouteMatcher DOT_SEPARATED_ROUTE_MATCHER = new PathPatternRouteMatcher();
46-
47-
private final String patternToMatch;
48-
49-
private final PathPatternRouteMatcher delegate;
44+
private final PathPatternParser parser;
5045

5146
/**
5247
* The {@link MessageMatcher} that determines if the type matches. If the type was
5348
* null, this matcher will match every Message.
5449
*/
5550
private MessageMatcher<Object> messageTypeMatcher = ANY_MESSAGE;
5651

57-
private DestinationPathPatternMessageMatcher(String pattern, PathPatternRouteMatcher matcher) {
58-
this.patternToMatch = pattern;
59-
this.delegate = matcher;
52+
private PathPatternMessageMatcher(PathPattern pattern, PathPatternParser parser) {
53+
this.parser = parser;
54+
this.pattern = pattern;
6055
}
6156

6257
/**
63-
* Initialize this builder with a {@link PathPatternRouteMatcher} configured with the
58+
* Initialize this builder with the {@link PathPatternParser#defaultInstance} that is
59+
* configured with the
6460
* {@link org.springframework.http.server.PathContainer.Options#HTTP_PATH} separator
6561
*/
6662
public static Builder withDefaults() {
67-
return new Builder(SLASH_SEPARATED_ROUTE_MATCHER);
63+
return new Builder(PathPatternParser.defaultInstance);
6864
}
6965

7066
/**
71-
* Initialize this builder with a {@link PathPatternRouteMatcher} configured with the
72-
* {@link org.springframework.http.server.PathContainer.Options#MESSAGE_ROUTE}
73-
* separator
67+
* Initialize this builder with the provided {@link PathPatternParser}
7468
*/
75-
public static Builder messageRoute() {
76-
return new Builder(DOT_SEPARATED_ROUTE_MATCHER);
69+
public static Builder withPathPatternParser(PathPatternParser parser) {
70+
return new Builder(parser);
7771
}
7872

7973
void setMessageTypeMatcher(MessageMatcher<Object> messageTypeMatcher) {
@@ -89,13 +83,13 @@ public boolean matches(Message<?> message) {
8983
return false;
9084
}
9185

92-
final String destination = getDestination(message);
86+
String destination = getDestination(message);
9387
if (destination == null) {
9488
return false;
9589
}
9690

97-
final RouteMatcher.Route destinationRoute = this.delegate.parseRoute(destination);
98-
return this.delegate.match(this.patternToMatch, destinationRoute);
91+
PathContainer destinationPathContainer = PathContainer.parsePath(destination, this.parser.getPathOptions());
92+
return this.pattern.matches(destinationPathContainer);
9993
}
10094

10195
/**
@@ -106,18 +100,18 @@ public boolean matches(Message<?> message) {
106100
* @throws IllegalStateException if the path does not match.
107101
*/
108102
public Map<String, String> extractPathVariables(Message<?> message) {
109-
final String destination = getDestination(message);
103+
String destination = getDestination(message);
110104
if (destination == null) {
111105
return Collections.emptyMap();
112106
}
113107

114-
final RouteMatcher.Route destinationRoute = this.delegate.parseRoute(destination);
115-
Map<String, String> pathMatchInfo = this.delegate.matchAndExtract(this.patternToMatch, destinationRoute);
108+
PathContainer destinationPathContainer = PathContainer.parsePath(destination, this.parser.getPathOptions());
109+
PathPattern.PathMatchInfo pathMatchInfo = this.pattern.matchAndExtract(destinationPathContainer);
116110

117111
Assert.state(pathMatchInfo != null,
118-
"Pattern \"" + this.patternToMatch + "\" is not a match for \"" + destination + "\"");
112+
"Pattern \"" + this.pattern.getPatternString() + "\" is not a match for \"" + destination + "\"");
119113

120-
return pathMatchInfo;
114+
return pathMatchInfo.getUriVariables();
121115
}
122116

123117
private static String getDestination(Message<?> message) {
@@ -126,30 +120,31 @@ private static String getDestination(Message<?> message) {
126120

127121
public static class Builder {
128122

129-
private final PathPatternRouteMatcher routeMatcher;
123+
private final PathPatternParser parser;
130124

131125
private MessageMatcher<Object> messageTypeMatcher = ANY_MESSAGE;
132126

133-
Builder(PathPatternRouteMatcher matcher) {
134-
this.routeMatcher = matcher;
135-
}
136-
137-
public Builder messageType(SimpMessageType type) {
138-
Assert.notNull(type, "Type must not be null");
139-
this.messageTypeMatcher = new SimpMessageTypeMatcher(type);
140-
return this;
127+
Builder(PathPatternParser parser) {
128+
this.parser = parser;
141129
}
142130

143-
public DestinationPathPatternMessageMatcher matcher(String pattern) {
131+
public PathPatternMessageMatcher matcher(String pattern) {
144132
Assert.notNull(pattern, "Pattern must not be null");
145-
DestinationPathPatternMessageMatcher matcher = new DestinationPathPatternMessageMatcher(pattern,
146-
this.routeMatcher);
133+
PathPattern pathPattern = this.parser.parse(pattern);
134+
PathPatternMessageMatcher matcher = new PathPatternMessageMatcher(pathPattern, this.parser);
147135
if (this.messageTypeMatcher != ANY_MESSAGE) {
148136
matcher.setMessageTypeMatcher(this.messageTypeMatcher);
149137
}
150138
return matcher;
151139
}
152140

141+
public PathPatternMessageMatcher matcher(String pattern, SimpMessageType type) {
142+
Assert.notNull(type, "Type must not be null");
143+
this.messageTypeMatcher = new SimpMessageTypeMatcher(type);
144+
145+
return matcher(pattern);
146+
}
147+
153148
}
154149

155150
}

messaging/src/main/java/org/springframework/security/messaging/util/matcher/SimpDestinationMessageMatcher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
*
3636
* @author Rob Winch
3737
* @since 4.0
38-
* @deprecated use {@link DestinationPathPatternMessageMatcher}
38+
* @deprecated use {@link PathPatternMessageMatcher}
3939
*/
4040
@Deprecated
4141
public final class SimpDestinationMessageMatcher implements MessageMatcher<Object> {

messaging/src/test/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManagerTests.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,17 @@ public final class MessageMatcherDelegatingAuthorizationManagerTests {
4343
void checkWhenPermitAllThenPermits() {
4444
AuthorizationManager<Message<?>> authorizationManager = builder().anyMessage().permitAll().build();
4545
Message<?> message = new GenericMessage<>(new Object());
46-
assertThat(authorizationManager.authorize(mock(Supplier.class), message).isGranted()).isTrue();
46+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
4747
}
4848

4949
@Test
5050
void checkWhenAnyMessageHasRoleThenRequires() {
5151
AuthorizationManager<Message<?>> authorizationManager = builder().anyMessage().hasRole("USER").build();
5252
Message<?> message = new GenericMessage<>(new Object());
5353
Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
54-
assertThat(authorizationManager.authorize(() -> user, message).isGranted()).isTrue();
54+
assertThat(authorizationManager.check(() -> user, message).isGranted()).isTrue();
5555
Authentication admin = new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
56-
assertThat(authorizationManager.authorize(() -> admin, message).isGranted()).isFalse();
56+
assertThat(authorizationManager.check(() -> admin, message).isGranted()).isFalse();
5757
}
5858

5959
@Test
@@ -66,7 +66,7 @@ void checkWhenSimpDestinationMatchesThenUses() {
6666
MessageHeaders headers = new MessageHeaders(
6767
Map.of(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/destination"));
6868
Message<?> message = new GenericMessage<>(new Object(), headers);
69-
assertThat(authorizationManager.authorize(mock(Supplier.class), message).isGranted()).isTrue();
69+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
7070
}
7171

7272
@Test
@@ -77,11 +77,11 @@ void checkWhenNullDestinationHeaderMatchesThenUses() {
7777
.denyAll()
7878
.build();
7979
Message<?> message = new GenericMessage<>(new Object());
80-
assertThat(authorizationManager.authorize(mock(Supplier.class), message).isGranted()).isTrue();
80+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
8181
MessageHeaders headers = new MessageHeaders(
8282
Map.of(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/destination"));
8383
message = new GenericMessage<>(new Object(), headers);
84-
assertThat(authorizationManager.authorize(mock(Supplier.class), message).isGranted()).isFalse();
84+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isFalse();
8585
}
8686

8787
@Test
@@ -94,7 +94,7 @@ void checkWhenSimpTypeMatchesThenUses() {
9494
MessageHeaders headers = new MessageHeaders(
9595
Map.of(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER, SimpMessageType.CONNECT));
9696
Message<?> message = new GenericMessage<>(new Object(), headers);
97-
assertThat(authorizationManager.authorize(mock(Supplier.class), message).isGranted()).isTrue();
97+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
9898
}
9999

100100
@Test
@@ -114,7 +114,7 @@ void checkWhenMessageTypeAndPathPatternMatches() {
114114
MessageHeaders headers2 = new MessageHeaders(Map.of(SimpMessageHeaderAccessor.MESSAGE_TYPE_HEADER,
115115
SimpMessageType.SUBSCRIBE, SimpMessageHeaderAccessor.DESTINATION_HEADER, "/destination"));
116116
Message<?> message2 = new GenericMessage<>(new Object(), headers2);
117-
assertThat(authorizationManager.authorize(mock(Supplier.class), message2).isGranted()).isFalse();
117+
assertThat(authorizationManager.check(mock(Supplier.class), message2).isGranted()).isFalse();
118118
}
119119

120120
@Test
@@ -128,7 +128,7 @@ void checkWhenMessageRouteAndPatternMismatch() {
128128
MessageHeaders headers = new MessageHeaders(
129129
Map.of(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/destination.sub.asdf"));
130130
Message<?> message = new GenericMessage<>(new Object(), headers);
131-
assertThat(authorizationManager.authorize(mock(Supplier.class), message).isGranted()).isFalse();
131+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isFalse();
132132
}
133133

134134
// gh-12540
@@ -142,7 +142,7 @@ void checkWhenSimpDestinationMatchesThenVariablesExtracted() {
142142
MessageHeaders headers = new MessageHeaders(
143143
Map.of(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/destination/3"));
144144
Message<?> message = new GenericMessage<>(new Object(), headers);
145-
assertThat(authorizationManager.authorize(mock(Supplier.class), message).isGranted()).isTrue();
145+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
146146
}
147147

148148
@Test
@@ -156,7 +156,7 @@ void checkWhenMessageRouteDestinationMatchesThenVariablesExtracted() {
156156
MessageHeaders headers = new MessageHeaders(
157157
Map.of(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/destination.3"));
158158
Message<?> message = new GenericMessage<>(new Object(), headers);
159-
assertThat(authorizationManager.authorize(mock(Supplier.class), message).isGranted()).isTrue();
159+
assertThat(authorizationManager.check(mock(Supplier.class), message).isGranted()).isTrue();
160160
}
161161

162162
private MessageMatcherDelegatingAuthorizationManager.Builder builder() {

0 commit comments

Comments
 (0)