Skip to content

Commit f4c0fcb

Browse files
committed
Add AuthorizationManager to Messaging
Closes gh-11076
1 parent bbff945 commit f4c0fcb

File tree

33 files changed

+2790
-71
lines changed

33 files changed

+2790
-71
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/messaging/MessageSecurityMetadataSourceRegistry.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -28,6 +28,7 @@
2828
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
2929
import org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler;
3030
import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory;
31+
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
3132
import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource;
3233
import org.springframework.security.messaging.util.matcher.MessageMatcher;
3334
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
@@ -43,7 +44,9 @@
4344
*
4445
* @author Rob Winch
4546
* @since 4.0
47+
* @deprecated Use {@link MessageMatcherDelegatingAuthorizationManager} instead
4648
*/
49+
@Deprecated
4750
public class MessageSecurityMetadataSourceRegistry {
4851

4952
private static final String permitAll = "permitAll";

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -81,9 +81,12 @@
8181
*
8282
* @author Rob Winch
8383
* @since 4.0
84+
* @see WebSocketMessageBrokerSecurityConfiguration
85+
* @deprecated Use {@link EnableWebSocketSecurity} instead
8486
*/
8587
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
8688
@Import(ObjectPostProcessorConfiguration.class)
89+
@Deprecated
8790
public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer
8891
implements WebSocketMessageBrokerConfigurer, SmartInitializingSingleton {
8992

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2002-2022 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.config.annotation.web.socket;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.context.annotation.Import;
26+
27+
/**
28+
* Allows configuring WebSocket Authorization.
29+
*
30+
* <p>
31+
* For example:
32+
* </p>
33+
*
34+
* <pre>
35+
* &#064;Configuration
36+
* &#064;EnableWebSocketSecurity
37+
* public class WebSocketSecurityConfig {
38+
*
39+
* &#064;Bean
40+
* AuthorizationManager&lt;Message&lt;?&gt;&gt; (MessageMatcherDelegatingAuthorizationManager.Builder messages) {
41+
* messages.simpDestMatchers(&quot;/user/queue/errors&quot;).permitAll()
42+
* .simpDestMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
43+
* .anyMessage().authenticated();
44+
* return messages.build();
45+
* }
46+
* }
47+
* </pre>
48+
*
49+
* @author Josh Cummings
50+
* @since 5.8
51+
*/
52+
@Retention(RetentionPolicy.RUNTIME)
53+
@Target(ElementType.TYPE)
54+
@Documented
55+
@Import(WebSocketMessageBrokerSecurityConfiguration.class)
56+
public @interface EnableWebSocketSecurity {
57+
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2002-2022 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.config.annotation.web.socket;
18+
19+
import org.springframework.context.ApplicationContext;
20+
import org.springframework.context.annotation.Bean;
21+
import org.springframework.context.annotation.Scope;
22+
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
23+
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
24+
import org.springframework.util.AntPathMatcher;
25+
26+
final class MessageMatcherAuthorizationManagerConfiguration {
27+
28+
@Bean
29+
@Scope("prototype")
30+
MessageMatcherDelegatingAuthorizationManager.Builder messageAuthorizationManagerBuilder(
31+
ApplicationContext context) {
32+
return MessageMatcherDelegatingAuthorizationManager.builder().simpDestPathMatcher(
33+
() -> (context.getBeanNamesForType(SimpAnnotationMethodMessageHandler.class).length > 0)
34+
? context.getBean(SimpAnnotationMethodMessageHandler.class).getPathMatcher()
35+
: new AntPathMatcher());
36+
}
37+
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2002-2022 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.config.annotation.web.socket;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import org.springframework.beans.factory.SmartInitializingSingleton;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.context.ApplicationContext;
26+
import org.springframework.context.annotation.Import;
27+
import org.springframework.core.Ordered;
28+
import org.springframework.core.annotation.Order;
29+
import org.springframework.messaging.Message;
30+
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
31+
import org.springframework.messaging.simp.config.ChannelRegistration;
32+
import org.springframework.messaging.support.ChannelInterceptor;
33+
import org.springframework.security.authorization.AuthorizationManager;
34+
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
35+
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
36+
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
37+
import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
38+
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
39+
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
40+
import org.springframework.security.messaging.web.socket.server.CsrfTokenHandshakeInterceptor;
41+
import org.springframework.util.Assert;
42+
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
43+
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
44+
import org.springframework.web.socket.server.HandshakeInterceptor;
45+
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
46+
import org.springframework.web.socket.sockjs.SockJsService;
47+
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
48+
import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService;
49+
50+
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
51+
@Import(MessageMatcherAuthorizationManagerConfiguration.class)
52+
final class WebSocketMessageBrokerSecurityConfiguration
53+
implements WebSocketMessageBrokerConfigurer, SmartInitializingSingleton {
54+
55+
private static final String SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME = "stompWebSocketHandlerMapping";
56+
57+
private MessageMatcherDelegatingAuthorizationManager b;
58+
59+
private static final AuthorizationManager<Message<?>> ANY_MESSAGE_AUTHENTICATED = MessageMatcherDelegatingAuthorizationManager
60+
.builder().anyMessage().authenticated().build();
61+
62+
private final ChannelInterceptor securityContextChannelInterceptor = new SecurityContextChannelInterceptor();
63+
64+
private final ChannelInterceptor csrfChannelInterceptor = new CsrfChannelInterceptor();
65+
66+
private AuthorizationChannelInterceptor authorizationChannelInterceptor = new AuthorizationChannelInterceptor(
67+
ANY_MESSAGE_AUTHENTICATED);
68+
69+
private ApplicationContext context;
70+
71+
WebSocketMessageBrokerSecurityConfiguration(ApplicationContext context) {
72+
this.context = context;
73+
}
74+
75+
@Override
76+
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
77+
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
78+
}
79+
80+
@Override
81+
public void configureClientInboundChannel(ChannelRegistration registration) {
82+
this.authorizationChannelInterceptor
83+
.setAuthorizationEventPublisher(new SpringAuthorizationEventPublisher(this.context));
84+
registration.interceptors(this.securityContextChannelInterceptor, this.csrfChannelInterceptor,
85+
this.authorizationChannelInterceptor);
86+
}
87+
88+
@Autowired(required = false)
89+
void setAuthorizationManager(AuthorizationManager<Message<?>> authorizationManager) {
90+
this.authorizationChannelInterceptor = new AuthorizationChannelInterceptor(authorizationManager);
91+
}
92+
93+
@Override
94+
public void afterSingletonsInstantiated() {
95+
SimpleUrlHandlerMapping mapping = getBeanOrNull(SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME,
96+
SimpleUrlHandlerMapping.class);
97+
if (mapping == null) {
98+
return;
99+
}
100+
configureCsrf(mapping);
101+
}
102+
103+
private <T> T getBeanOrNull(String name, Class<T> type) {
104+
Map<String, T> beans = this.context.getBeansOfType(type);
105+
return beans.get(name);
106+
}
107+
108+
private void configureCsrf(SimpleUrlHandlerMapping mapping) {
109+
Map<String, Object> mappings = mapping.getHandlerMap();
110+
for (Object object : mappings.values()) {
111+
if (object instanceof SockJsHttpRequestHandler) {
112+
setHandshakeInterceptors((SockJsHttpRequestHandler) object);
113+
}
114+
else if (object instanceof WebSocketHttpRequestHandler) {
115+
setHandshakeInterceptors((WebSocketHttpRequestHandler) object);
116+
}
117+
else {
118+
throw new IllegalStateException(
119+
"Bean " + SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME + " is expected to contain mappings to either a "
120+
+ "SockJsHttpRequestHandler or a WebSocketHttpRequestHandler but got " + object);
121+
}
122+
}
123+
}
124+
125+
private void setHandshakeInterceptors(SockJsHttpRequestHandler handler) {
126+
SockJsService sockJsService = handler.getSockJsService();
127+
Assert.state(sockJsService instanceof TransportHandlingSockJsService,
128+
() -> "sockJsService must be instance of TransportHandlingSockJsService got " + sockJsService);
129+
TransportHandlingSockJsService transportHandlingSockJsService = (TransportHandlingSockJsService) sockJsService;
130+
List<HandshakeInterceptor> handshakeInterceptors = transportHandlingSockJsService.getHandshakeInterceptors();
131+
List<HandshakeInterceptor> interceptorsToSet = new ArrayList<>(handshakeInterceptors.size() + 1);
132+
interceptorsToSet.add(new CsrfTokenHandshakeInterceptor());
133+
interceptorsToSet.addAll(handshakeInterceptors);
134+
transportHandlingSockJsService.setHandshakeInterceptors(interceptorsToSet);
135+
}
136+
137+
private void setHandshakeInterceptors(WebSocketHttpRequestHandler handler) {
138+
List<HandshakeInterceptor> handshakeInterceptors = handler.getHandshakeInterceptors();
139+
List<HandshakeInterceptor> interceptorsToSet = new ArrayList<>(handshakeInterceptors.size() + 1);
140+
interceptorsToSet.add(new CsrfTokenHandshakeInterceptor());
141+
interceptorsToSet.addAll(handshakeInterceptors);
142+
handler.setHandshakeInterceptors(interceptorsToSet);
143+
}
144+
145+
}

0 commit comments

Comments
 (0)