1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Add AuthorizationManager to Messaging

Closes gh-11076
This commit is contained in:
Josh Cummings
2022-04-07 17:39:10 -06:00
parent bbff945b95
commit f4c0fcb5ef
33 changed files with 2801 additions and 82 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
import org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler;
import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory;
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource;
import org.springframework.security.messaging.util.matcher.MessageMatcher;
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
@@ -43,7 +44,9 @@ import org.springframework.util.StringUtils;
*
* @author Rob Winch
* @since 4.0
* @deprecated Use {@link MessageMatcherDelegatingAuthorizationManager} instead
*/
@Deprecated
public class MessageSecurityMetadataSourceRegistry {
private static final String permitAll = "permitAll";
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -81,9 +81,12 @@ import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsSe
*
* @author Rob Winch
* @since 4.0
* @see WebSocketMessageBrokerSecurityConfiguration
* @deprecated Use {@link EnableWebSocketSecurity} instead
*/
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
@Import(ObjectPostProcessorConfiguration.class)
@Deprecated
public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer
implements WebSocketMessageBrokerConfigurer, SmartInitializingSingleton {
@@ -0,0 +1,58 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.socket;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Allows configuring WebSocket Authorization.
*
* <p>
* For example:
* </p>
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSocketSecurity
* public class WebSocketSecurityConfig {
*
* &#064;Bean
* AuthorizationManager&lt;Message&lt;?&gt;&gt; (MessageMatcherDelegatingAuthorizationManager.Builder messages) {
* messages.simpDestMatchers(&quot;/user/queue/errors&quot;).permitAll()
* .simpDestMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
* .anyMessage().authenticated();
* return messages.build();
* }
* }
* </pre>
*
* @author Josh Cummings
* @since 5.8
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(WebSocketMessageBrokerSecurityConfiguration.class)
public @interface EnableWebSocketSecurity {
}
@@ -0,0 +1,38 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.socket;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
import org.springframework.util.AntPathMatcher;
final class MessageMatcherAuthorizationManagerConfiguration {
@Bean
@Scope("prototype")
MessageMatcherDelegatingAuthorizationManager.Builder messageAuthorizationManagerBuilder(
ApplicationContext context) {
return MessageMatcherDelegatingAuthorizationManager.builder().simpDestPathMatcher(
() -> (context.getBeanNamesForType(SimpAnnotationMethodMessageHandler.class).length > 0)
? context.getBean(SimpAnnotationMethodMessageHandler.class).getPathMatcher()
: new AntPathMatcher());
}
}
@@ -0,0 +1,145 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
import org.springframework.security.messaging.web.socket.server.CsrfTokenHandshakeInterceptor;
import org.springframework.util.Assert;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsService;
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService;
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
@Import(MessageMatcherAuthorizationManagerConfiguration.class)
final class WebSocketMessageBrokerSecurityConfiguration
implements WebSocketMessageBrokerConfigurer, SmartInitializingSingleton {
private static final String SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME = "stompWebSocketHandlerMapping";
private MessageMatcherDelegatingAuthorizationManager b;
private static final AuthorizationManager<Message<?>> ANY_MESSAGE_AUTHENTICATED = MessageMatcherDelegatingAuthorizationManager
.builder().anyMessage().authenticated().build();
private final ChannelInterceptor securityContextChannelInterceptor = new SecurityContextChannelInterceptor();
private final ChannelInterceptor csrfChannelInterceptor = new CsrfChannelInterceptor();
private AuthorizationChannelInterceptor authorizationChannelInterceptor = new AuthorizationChannelInterceptor(
ANY_MESSAGE_AUTHENTICATED);
private ApplicationContext context;
WebSocketMessageBrokerSecurityConfiguration(ApplicationContext context) {
this.context = context;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
this.authorizationChannelInterceptor
.setAuthorizationEventPublisher(new SpringAuthorizationEventPublisher(this.context));
registration.interceptors(this.securityContextChannelInterceptor, this.csrfChannelInterceptor,
this.authorizationChannelInterceptor);
}
@Autowired(required = false)
void setAuthorizationManager(AuthorizationManager<Message<?>> authorizationManager) {
this.authorizationChannelInterceptor = new AuthorizationChannelInterceptor(authorizationManager);
}
@Override
public void afterSingletonsInstantiated() {
SimpleUrlHandlerMapping mapping = getBeanOrNull(SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME,
SimpleUrlHandlerMapping.class);
if (mapping == null) {
return;
}
configureCsrf(mapping);
}
private <T> T getBeanOrNull(String name, Class<T> type) {
Map<String, T> beans = this.context.getBeansOfType(type);
return beans.get(name);
}
private void configureCsrf(SimpleUrlHandlerMapping mapping) {
Map<String, Object> mappings = mapping.getHandlerMap();
for (Object object : mappings.values()) {
if (object instanceof SockJsHttpRequestHandler) {
setHandshakeInterceptors((SockJsHttpRequestHandler) object);
}
else if (object instanceof WebSocketHttpRequestHandler) {
setHandshakeInterceptors((WebSocketHttpRequestHandler) object);
}
else {
throw new IllegalStateException(
"Bean " + SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME + " is expected to contain mappings to either a "
+ "SockJsHttpRequestHandler or a WebSocketHttpRequestHandler but got " + object);
}
}
}
private void setHandshakeInterceptors(SockJsHttpRequestHandler handler) {
SockJsService sockJsService = handler.getSockJsService();
Assert.state(sockJsService instanceof TransportHandlingSockJsService,
() -> "sockJsService must be instance of TransportHandlingSockJsService got " + sockJsService);
TransportHandlingSockJsService transportHandlingSockJsService = (TransportHandlingSockJsService) sockJsService;
List<HandshakeInterceptor> handshakeInterceptors = transportHandlingSockJsService.getHandshakeInterceptors();
List<HandshakeInterceptor> interceptorsToSet = new ArrayList<>(handshakeInterceptors.size() + 1);
interceptorsToSet.add(new CsrfTokenHandshakeInterceptor());
interceptorsToSet.addAll(handshakeInterceptors);
transportHandlingSockJsService.setHandshakeInterceptors(interceptorsToSet);
}
private void setHandshakeInterceptors(WebSocketHttpRequestHandler handler) {
List<HandshakeInterceptor> handshakeInterceptors = handler.getHandshakeInterceptors();
List<HandshakeInterceptor> interceptorsToSet = new ArrayList<>(handshakeInterceptors.size() + 1);
interceptorsToSet.add(new CsrfTokenHandshakeInterceptor());
interceptorsToSet.addAll(handshakeInterceptors);
handler.setHandshakeInterceptors(interceptorsToSet);
}
}
@@ -19,6 +19,7 @@ package org.springframework.security.config.websocket;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.w3c.dom.Element;
@@ -37,20 +38,34 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.messaging.Message;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.vote.ConsensusBased;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.Elements;
import org.springframework.security.core.Authentication;
import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory;
import org.springframework.security.messaging.access.expression.MessageAuthorizationContextSecurityExpressionHandler;
import org.springframework.security.messaging.access.expression.MessageExpressionVoter;
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
import org.springframework.security.messaging.access.intercept.ChannelSecurityInterceptor;
import org.springframework.security.messaging.access.intercept.MessageAuthorizationContext;
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
import org.springframework.security.messaging.util.matcher.MessageMatcher;
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher;
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
import org.springframework.security.messaging.web.socket.server.CsrfTokenHandshakeInterceptor;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
@@ -99,6 +114,10 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
private static final String DISABLED_ATTR = "same-origin-disabled";
private static final String USE_AUTHORIZATION_MANAGER_ATTR = "use-authorization-manager";
private static final String AUTHORIZATION_MANAGER_REF_ATTR = "authorization-manager-ref";
private static final String PATTERN_ATTR = "pattern";
private static final String ACCESS_ATTR = "access";
@@ -114,14 +133,83 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
*/
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String id = element.getAttribute(ID_ATTR);
String inSecurityInterceptorName = parseAuthorization(element, parserContext);
BeanDefinitionRegistry registry = parserContext.getRegistry();
if (StringUtils.hasText(id)) {
registry.registerAlias(inSecurityInterceptorName, id);
if (!registry.containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
registry.registerBeanDefinition(PATH_MATCHER_BEAN_NAME, new RootBeanDefinition(AntPathMatcher.class));
}
}
else {
boolean sameOriginDisabled = Boolean.parseBoolean(element.getAttribute(DISABLED_ATTR));
XmlReaderContext context = parserContext.getReaderContext();
BeanDefinitionBuilder mspp = BeanDefinitionBuilder.rootBeanDefinition(MessageSecurityPostProcessor.class);
mspp.addConstructorArgValue(inSecurityInterceptorName);
mspp.addConstructorArgValue(sameOriginDisabled);
context.registerWithGeneratedName(mspp.getBeanDefinition());
}
return null;
}
private String parseAuthorization(Element element, ParserContext parserContext) {
boolean useAuthorizationManager = Boolean.parseBoolean(element.getAttribute(USE_AUTHORIZATION_MANAGER_ATTR));
if (useAuthorizationManager) {
return parseAuthorizationManager(element, parserContext);
}
if (StringUtils.hasText(element.getAttribute(AUTHORIZATION_MANAGER_REF_ATTR))) {
return parseAuthorizationManager(element, parserContext);
}
return parseSecurityMetadataSource(element, parserContext);
}
private String parseAuthorizationManager(Element element, ParserContext parserContext) {
XmlReaderContext context = parserContext.getReaderContext();
String mdsId = createAuthorizationManager(element, parserContext);
BeanDefinitionBuilder inboundChannelSecurityInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(AuthorizationChannelInterceptor.class);
inboundChannelSecurityInterceptor.addConstructorArgReference(mdsId);
return context.registerWithGeneratedName(inboundChannelSecurityInterceptor.getBeanDefinition());
}
private String createAuthorizationManager(Element element, ParserContext parserContext) {
XmlReaderContext context = parserContext.getReaderContext();
String authorizationManagerRef = element.getAttribute(AUTHORIZATION_MANAGER_REF_ATTR);
if (StringUtils.hasText(authorizationManagerRef)) {
return authorizationManagerRef;
}
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
String expressionHandlerRef = (expressionHandlerElt != null) ? expressionHandlerElt.getAttribute("ref") : null;
ManagedMap<BeanDefinition, BeanDefinition> matcherToExpression = new ManagedMap<>();
List<Element> interceptMessages = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_MESSAGE);
for (Element interceptMessage : interceptMessages) {
String matcherPattern = interceptMessage.getAttribute(PATTERN_ATTR);
String accessExpression = interceptMessage.getAttribute(ACCESS_ATTR);
String messageType = interceptMessage.getAttribute(TYPE_ATTR);
BeanDefinition matcher = createMatcher(matcherPattern, messageType, parserContext, interceptMessage);
BeanDefinitionBuilder authorizationManager = BeanDefinitionBuilder
.rootBeanDefinition(ExpressionBasedAuthorizationManager.class);
if (StringUtils.hasText(expressionHandlerRef)) {
authorizationManager.addConstructorArgReference(expressionHandlerRef);
}
authorizationManager.addConstructorArgValue(accessExpression);
matcherToExpression.put(matcher, authorizationManager.getBeanDefinition());
}
BeanDefinitionBuilder mds = BeanDefinitionBuilder
.rootBeanDefinition(MessageMatcherDelegatingAuthorizationManagerFactory.class);
mds.setFactoryMethod("createMessageMatcherDelegatingAuthorizationManager");
mds.addConstructorArgValue(matcherToExpression);
return context.registerWithGeneratedName(mds.getBeanDefinition());
}
private String parseSecurityMetadataSource(Element element, ParserContext parserContext) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
XmlReaderContext context = parserContext.getReaderContext();
ManagedMap<BeanDefinition, String> matcherToExpression = new ManagedMap<>();
String id = element.getAttribute(ID_ATTR);
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
String expressionHandlerRef = (expressionHandlerElt != null) ? expressionHandlerElt.getAttribute("ref") : null;
boolean expressionHandlerDefined = StringUtils.hasText(expressionHandlerRef);
boolean sameOriginDisabled = Boolean.parseBoolean(element.getAttribute(DISABLED_ATTR));
List<Element> interceptMessages = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_MESSAGE);
for (Element interceptMessage : interceptMessages) {
String matcherPattern = interceptMessage.getAttribute(PATTERN_ATTR);
@@ -151,21 +239,7 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
.rootBeanDefinition(ChannelSecurityInterceptor.class);
inboundChannelSecurityInterceptor.addConstructorArgValue(registry.getBeanDefinition(mdsId));
inboundChannelSecurityInterceptor.addPropertyValue("accessDecisionManager", adm.getBeanDefinition());
String inSecurityInterceptorName = context
.registerWithGeneratedName(inboundChannelSecurityInterceptor.getBeanDefinition());
if (StringUtils.hasText(id)) {
registry.registerAlias(inSecurityInterceptorName, id);
if (!registry.containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
registry.registerBeanDefinition(PATH_MATCHER_BEAN_NAME, new RootBeanDefinition(AntPathMatcher.class));
}
}
else {
BeanDefinitionBuilder mspp = BeanDefinitionBuilder.rootBeanDefinition(MessageSecurityPostProcessor.class);
mspp.addConstructorArgValue(inSecurityInterceptorName);
mspp.addConstructorArgValue(sameOriginDisabled);
context.registerWithGeneratedName(mspp.getBeanDefinition());
}
return null;
return context.registerWithGeneratedName(inboundChannelSecurityInterceptor.getBeanDefinition());
}
private BeanDefinition createMatcher(String matcherPattern, String messageType, ParserContext parserContext,
@@ -341,4 +415,48 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
}
private static final class ExpressionBasedAuthorizationManager
implements AuthorizationManager<MessageAuthorizationContext<?>> {
private final SecurityExpressionHandler<MessageAuthorizationContext<?>> expressionHandler;
private final Expression expression;
private ExpressionBasedAuthorizationManager(String expression) {
this(new MessageAuthorizationContextSecurityExpressionHandler(), expression);
}
private ExpressionBasedAuthorizationManager(
SecurityExpressionHandler<MessageAuthorizationContext<?>> expressionHandler, String expression) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
Assert.notNull(expression, "expression cannot be null");
this.expressionHandler = expressionHandler;
this.expression = this.expressionHandler.getExpressionParser().parseExpression(expression);
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication,
MessageAuthorizationContext<?> object) {
EvaluationContext context = this.expressionHandler.createEvaluationContext(authentication, object);
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, context);
return new AuthorizationDecision(granted);
}
}
private static class MessageMatcherDelegatingAuthorizationManagerFactory {
private static AuthorizationManager<Message<?>> createMessageMatcherDelegatingAuthorizationManager(
Map<MessageMatcher<?>, AuthorizationManager<MessageAuthorizationContext<?>>> beans) {
MessageMatcherDelegatingAuthorizationManager.Builder builder = MessageMatcherDelegatingAuthorizationManager
.builder();
for (Map.Entry<MessageMatcher<?>, AuthorizationManager<MessageAuthorizationContext<?>>> entry : beans
.entrySet()) {
builder.matchers(entry.getKey()).access(entry.getValue());
}
return builder.anyMessage().permitAll().build();
}
}
}
@@ -291,6 +291,12 @@ websocket-message-broker.attrlist &=
websocket-message-broker.attrlist &=
## Disables the requirement for CSRF token to be present in the Stomp headers (default false). Changing the default is useful if it is necessary to allow other origins to make SockJS connections.
attribute same-origin-disabled {xsd:boolean}?
websocket-message-broker.attlist &=
## Use this AuthorizationManager instead of deriving one from <intercept-message> elements
attribute authorization-manager-ref {xsd:string}?
websocket-message-broker.attrlist &=
## Use AuthorizationManager API instead of SecurityMetadatasource
attribute use-authorization-manager {xsd:boolean}?
intercept-message =
## Creates an authorization rule for a websocket message.
@@ -915,6 +915,20 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="use-authorization-manager" type="xs:boolean">
<xs:annotation>
<xs:documentation>Use AuthorizationManager API instead of SecurityMetadatasource
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="websocket-message-broker.attlist">
<xs:attribute name="authorization-manager-ref" type="xs:string">
<xs:annotation>
<xs:documentation>Use this AuthorizationManager instead of deriving one from &lt;intercept-message&gt; elements
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="intercept-message">
<xs:annotation>
@@ -291,6 +291,12 @@ websocket-message-broker.attrlist &=
websocket-message-broker.attrlist &=
## Disables the requirement for CSRF token to be present in the Stomp headers (default false). Changing the default is useful if it is necessary to allow other origins to make SockJS connections.
attribute same-origin-disabled {xsd:boolean}?
websocket-message-broker.attlist &=
## Use this AuthorizationManager instead of deriving one from <intercept-message> elements
attribute authorization-manager-ref {xsd:string}?
websocket-message-broker.attrlist &=
## Use AuthorizationManager API instead of SecurityMetadatasource
attribute use-authorization-manager {xsd:boolean}?
intercept-message =
## Creates an authorization rule for a websocket message.
@@ -915,6 +915,20 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="use-authorization-manager" type="xs:boolean">
<xs:annotation>
<xs:documentation>Use AuthorizationManager API instead of SecurityMetadatasource
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="websocket-message-broker.attlist">
<xs:attribute name="authorization-manager-ref" type="xs:string">
<xs:annotation>
<xs:documentation>Use this AuthorizationManager instead of deriving one from &lt;intercept-message&gt; elements
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="intercept-message">
<xs:annotation>
@@ -0,0 +1,176 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.socket;
import java.util.HashMap;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
public class WebSocketMessageBrokerSecurityConfigurationDocTests {
AnnotationConfigWebApplicationContext context;
TestingAuthenticationToken messageUser;
CsrfToken token;
String sessionAttr;
@BeforeEach
public void setup() {
this.token = new DefaultCsrfToken("header", "param", "token");
this.sessionAttr = "sessionAttr";
this.messageUser = new TestingAuthenticationToken("user", "pass", "ROLE_USER");
}
@AfterEach
public void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void securityMappings() {
loadConfig(WebSocketSecurityConfig.class);
clientInboundChannel().send(message("/user/queue/errors", SimpMessageType.SUBSCRIBE));
assertThatExceptionOfType(MessageDeliveryException.class)
.isThrownBy(() -> clientInboundChannel().send(message("/denyAll", SimpMessageType.MESSAGE)))
.withCauseInstanceOf(AccessDeniedException.class);
}
private void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
this.context.register(WebSocketConfig.class, SyncExecutorConfig.class);
this.context.setServletConfig(new MockServletConfig());
this.context.refresh();
}
private MessageChannel clientInboundChannel() {
return this.context.getBean("clientInboundChannel", MessageChannel.class);
}
private Message<String> message(String destination, SimpMessageType type) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(type);
return message(headers, destination);
}
private Message<String> message(SimpMessageHeaderAccessor headers, String destination) {
headers.setSessionId("123");
headers.setSessionAttributes(new HashMap<>());
if (destination != null) {
headers.setDestination(destination);
}
if (this.messageUser != null) {
headers.setUser(this.messageUser);
}
return new GenericMessage<>("hi", headers.getMessageHeaders());
}
@Controller
static class MyController {
@MessageMapping("/authentication")
void authentication(@AuthenticationPrincipal String un) {
// ... do something ...
}
}
@Configuration
@EnableWebSocketSecurity
static class WebSocketSecurityConfig {
@Bean
AuthorizationManager<Message<?>> authorizationManager(
MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages.nullDestMatcher().authenticated()
// <1>
.simpSubscribeDestMatchers("/user/queue/errors").permitAll()
// <2>
.simpDestMatchers("/app/**").hasRole("USER")
// <3>
.simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") // <4>
.simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE).denyAll() // <5>
.anyMessage().denyAll(); // <6>
return messages.build();
}
}
@Configuration
@EnableWebSocketMessageBroker
static class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll");
}
@Bean
MyController myController() {
return new MyController();
}
}
@Configuration
static class SyncExecutorConfig {
@Bean
static SyncExecutorSubscribableChannelPostProcessor postProcessor() {
return new SyncExecutorSubscribableChannelPostProcessor();
}
}
}
@@ -0,0 +1,785 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.support.AbstractMessageChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
import org.springframework.security.messaging.access.intercept.MessageAuthorizationContext;
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.stereotype.Controller;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.HandshakeFailureException;
import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler;
import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
public class WebSocketMessageBrokerSecurityConfigurationTests {
AnnotationConfigWebApplicationContext context;
TestingAuthenticationToken messageUser;
CsrfToken token;
String sessionAttr;
@BeforeEach
public void setup() {
this.token = new DefaultCsrfToken("header", "param", "token");
this.sessionAttr = "sessionAttr";
this.messageUser = new TestingAuthenticationToken("user", "pass", "ROLE_USER");
}
@AfterEach
public void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void simpleRegistryMappings() {
loadConfig(SockJsSecurityConfig.class);
clientInboundChannel().send(message("/permitAll"));
assertThatExceptionOfType(MessageDeliveryException.class)
.isThrownBy(() -> clientInboundChannel().send(message("/denyAll")))
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void annonymousSupported() {
loadConfig(SockJsSecurityConfig.class);
this.messageUser = null;
clientInboundChannel().send(message("/permitAll"));
}
// gh-3797
@Test
public void beanResolver() {
loadConfig(SockJsSecurityConfig.class);
this.messageUser = null;
clientInboundChannel().send(message("/beanResolver"));
}
@Test
public void addsAuthenticationPrincipalResolver() {
loadConfig(SockJsSecurityConfig.class);
MessageChannel messageChannel = clientInboundChannel();
Message<String> message = message("/permitAll/authentication");
messageChannel.send(message);
assertThat(this.context.getBean(MyController.class).authenticationPrincipal)
.isEqualTo((String) this.messageUser.getPrincipal());
}
@Test
public void addsAuthenticationPrincipalResolverWhenNoAuthorization() {
loadConfig(NoInboundSecurityConfig.class);
MessageChannel messageChannel = clientInboundChannel();
Message<String> message = message("/permitAll/authentication");
messageChannel.send(message);
assertThat(this.context.getBean(MyController.class).authenticationPrincipal)
.isEqualTo((String) this.messageUser.getPrincipal());
}
@Test
public void addsCsrfProtectionWhenNoAuthorization() {
loadConfig(NoInboundSecurityConfig.class);
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
Message<?> message = message(headers, "/authentication");
MessageChannel messageChannel = clientInboundChannel();
assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() -> messageChannel.send(message))
.withCauseInstanceOf(MissingCsrfTokenException.class);
}
@Test
public void csrfProtectionForConnect() {
loadConfig(SockJsSecurityConfig.class);
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
Message<?> message = message(headers, "/authentication");
MessageChannel messageChannel = clientInboundChannel();
assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() -> messageChannel.send(message))
.withCauseInstanceOf(MissingCsrfTokenException.class);
}
@Test
@Disabled // to be added back in with the introduction of DSL support
public void csrfProtectionDisabledForConnect() {
loadConfig(CsrfDisabledSockJsSecurityConfig.class);
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
Message<?> message = message(headers, "/permitAll/connect");
MessageChannel messageChannel = clientInboundChannel();
messageChannel.send(message);
}
@Test
public void csrfProtectionDefinedByBean() {
loadConfig(SockJsProxylessSecurityConfig.class);
MessageChannel messageChannel = clientInboundChannel();
Stream<Class<? extends ChannelInterceptor>> interceptors = ((AbstractMessageChannel) messageChannel)
.getInterceptors().stream().map(ChannelInterceptor::getClass);
assertThat(interceptors).contains(CsrfChannelInterceptor.class);
}
@Test
public void messagesConnectUseCsrfTokenHandshakeInterceptor() throws Exception {
loadConfig(SockJsSecurityConfig.class);
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
Message<?> message = message(headers, "/authentication");
MockHttpServletRequest request = sockjsHttpRequest("/chat");
HttpRequestHandler handler = handler(request);
handler.handleRequest(request, new MockHttpServletResponse());
assertHandshake(request);
}
@Test
public void messagesConnectUseCsrfTokenHandshakeInterceptorMultipleMappings() throws Exception {
loadConfig(SockJsSecurityConfig.class);
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
Message<?> message = message(headers, "/authentication");
MockHttpServletRequest request = sockjsHttpRequest("/other");
HttpRequestHandler handler = handler(request);
handler.handleRequest(request, new MockHttpServletResponse());
assertHandshake(request);
}
@Test
public void messagesConnectWebSocketUseCsrfTokenHandshakeInterceptor() throws Exception {
loadConfig(WebSocketSecurityConfig.class);
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
Message<?> message = message(headers, "/authentication");
MockHttpServletRequest request = websocketHttpRequest("/websocket");
HttpRequestHandler handler = handler(request);
handler.handleRequest(request, new MockHttpServletResponse());
assertHandshake(request);
}
@Test
public void msmsRegistryCustomPatternMatcher() {
loadConfig(MsmsRegistryCustomPatternMatcherConfig.class);
clientInboundChannel().send(message("/app/a.b"));
assertThatExceptionOfType(MessageDeliveryException.class)
.isThrownBy(() -> clientInboundChannel().send(message("/app/a.b.c")))
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void overrideMsmsRegistryCustomPatternMatcher() {
loadConfig(OverrideMsmsRegistryCustomPatternMatcherConfig.class);
clientInboundChannel().send(message("/app/a/b"));
assertThatExceptionOfType(MessageDeliveryException.class)
.isThrownBy(() -> clientInboundChannel().send(message("/app/a/b/c")))
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void defaultPatternMatcher() {
loadConfig(DefaultPatternMatcherConfig.class);
clientInboundChannel().send(message("/app/a/b"));
assertThatExceptionOfType(MessageDeliveryException.class)
.isThrownBy(() -> clientInboundChannel().send(message("/app/a/b/c")))
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void customExpression() {
loadConfig(CustomExpressionConfig.class);
clientInboundChannel().send(message("/denyRob"));
this.messageUser = new TestingAuthenticationToken("rob", "password", "ROLE_USER");
assertThatExceptionOfType(MessageDeliveryException.class)
.isThrownBy(() -> clientInboundChannel().send(message("/denyRob")))
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void channelSecurityInterceptorUsesMetadataSourceBeanWhenProxyingDisabled() {
loadConfig(SockJsProxylessSecurityConfig.class);
AbstractMessageChannel messageChannel = clientInboundChannel();
AuthorizationManager<Message<?>> authorizationManager = this.context.getBean(AuthorizationManager.class);
for (ChannelInterceptor interceptor : messageChannel.getInterceptors()) {
if (interceptor instanceof AuthorizationChannelInterceptor) {
assertThat(ReflectionTestUtils.getField(interceptor, "preSendAuthorizationManager"))
.isSameAs(authorizationManager);
return;
}
}
fail("did not find AuthorizationChannelInterceptor");
}
@Test
public void securityContextChannelInterceptorDefinedByBean() {
loadConfig(SockJsProxylessSecurityConfig.class);
MessageChannel messageChannel = clientInboundChannel();
Stream<Class<? extends ChannelInterceptor>> interceptors = ((AbstractMessageChannel) messageChannel)
.getInterceptors().stream().map(ChannelInterceptor::getClass);
assertThat(interceptors).contains(SecurityContextChannelInterceptor.class);
}
@Test
public void inboundChannelSecurityDefinedByBean() {
loadConfig(SockJsProxylessSecurityConfig.class);
MessageChannel messageChannel = clientInboundChannel();
Stream<Class<? extends ChannelInterceptor>> interceptors = ((AbstractMessageChannel) messageChannel)
.getInterceptors().stream().map(ChannelInterceptor::getClass);
assertThat(interceptors).contains(AuthorizationChannelInterceptor.class);
}
private void assertHandshake(HttpServletRequest request) {
TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class);
assertThat(handshakeHandler.attributes.get(CsrfToken.class.getName())).isSameAs(this.token);
assertThat(handshakeHandler.attributes.get(this.sessionAttr))
.isEqualTo(request.getSession().getAttribute(this.sessionAttr));
}
private HttpRequestHandler handler(HttpServletRequest request) throws Exception {
HandlerMapping handlerMapping = this.context.getBean(HandlerMapping.class);
return (HttpRequestHandler) handlerMapping.getHandler(request).getHandler();
}
private MockHttpServletRequest websocketHttpRequest(String mapping) {
MockHttpServletRequest request = sockjsHttpRequest(mapping);
request.setRequestURI(mapping);
return request;
}
private MockHttpServletRequest sockjsHttpRequest(String mapping) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
request.setMethod("GET");
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/289/tpyx6mde/websocket");
request.setRequestURI(mapping + "/289/tpyx6mde/websocket");
request.getSession().setAttribute(this.sessionAttr, "sessionValue");
request.setAttribute(CsrfToken.class.getName(), this.token);
return request;
}
private Message<String> message(String destination) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
return message(headers, destination);
}
private Message<String> message(SimpMessageHeaderAccessor headers, String destination) {
headers.setSessionId("123");
headers.setSessionAttributes(new HashMap<>());
if (destination != null) {
headers.setDestination(destination);
}
if (this.messageUser != null) {
headers.setUser(this.messageUser);
}
return new GenericMessage<>("hi", headers.getMessageHeaders());
}
private <T extends MessageChannel> T clientInboundChannel() {
return (T) this.context.getBean("clientInboundChannel", MessageChannel.class);
}
private void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
this.context.setServletConfig(new MockServletConfig());
this.context.refresh();
}
@Configuration
@EnableWebSocketMessageBroker
@EnableWebSocketSecurity
@Import(SyncExecutorConfig.class)
static class MsmsRegistryCustomPatternMatcherConfig implements WebSocketMessageBrokerConfigurer {
// @formatter:off
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/other")
.setHandshakeHandler(testHandshakeHandler());
}
// @formatter:on
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setPathMatcher(new AntPathMatcher("."));
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
// @formatter:off
@Bean
AuthorizationManager<Message<?>> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
.simpDestMatchers("/app/a.*").permitAll()
.anyMessage().denyAll();
return messages.build();
}
// @formatter:on
@Bean
TestHandshakeHandler testHandshakeHandler() {
return new TestHandshakeHandler();
}
}
@Configuration
@EnableWebSocketMessageBroker
@EnableWebSocketSecurity
@Import(SyncExecutorConfig.class)
static class OverrideMsmsRegistryCustomPatternMatcherConfig implements WebSocketMessageBrokerConfigurer {
// @formatter:off
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/other")
.setHandshakeHandler(testHandshakeHandler());
}
// @formatter:on
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setPathMatcher(new AntPathMatcher("."));
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
// @formatter:off
@Bean
AuthorizationManager<Message<?>> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
.simpDestPathMatcher(new AntPathMatcher())
.simpDestMatchers("/app/a/*").permitAll()
.anyMessage().denyAll();
return messages.build();
}
// @formatter:on
@Bean
TestHandshakeHandler testHandshakeHandler() {
return new TestHandshakeHandler();
}
}
@Configuration
@EnableWebSocketMessageBroker
@EnableWebSocketSecurity
@Import(SyncExecutorConfig.class)
static class DefaultPatternMatcherConfig implements WebSocketMessageBrokerConfigurer {
// @formatter:off
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/other")
.setHandshakeHandler(testHandshakeHandler());
}
// @formatter:on
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
// @formatter:off
@Bean
AuthorizationManager<Message<?>> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
.simpDestMatchers("/app/a/*").permitAll()
.anyMessage().denyAll();
return messages.build();
}
// @formatter:on
@Bean
TestHandshakeHandler testHandshakeHandler() {
return new TestHandshakeHandler();
}
}
@Configuration
@EnableWebSocketMessageBroker
@EnableWebSocketSecurity
@Import(SyncExecutorConfig.class)
static class CustomExpressionConfig implements WebSocketMessageBrokerConfigurer {
// @formatter:off
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/other")
.setHandshakeHandler(testHandshakeHandler());
}
// @formatter:on
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
@Bean
AuthorizationManager<Message<Object>> authorizationManager() {
return (authentication, message) -> {
Authentication auth = authentication.get();
return new AuthorizationDecision(auth != null && !"rob".equals(auth.getName()));
};
}
@Bean
TestHandshakeHandler testHandshakeHandler() {
return new TestHandshakeHandler();
}
}
@Controller
static class MyController {
String authenticationPrincipal;
MyCustomArgument myCustomArgument;
@MessageMapping("/authentication")
void authentication(@AuthenticationPrincipal String un) {
this.authenticationPrincipal = un;
}
@MessageMapping("/myCustom")
void myCustom(MyCustomArgument myCustomArgument) {
this.myCustomArgument = myCustomArgument;
}
}
static class MyCustomArgument {
MyCustomArgument(String notDefaultConstr) {
}
}
static class MyCustomArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(MyCustomArgument.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) {
return new MyCustomArgument("");
}
}
static class TestHandshakeHandler implements HandshakeHandler {
Map<String, Object> attributes;
@Override
public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws HandshakeFailureException {
this.attributes = attributes;
if (wsHandler instanceof SockJsWebSocketHandler) {
// work around SPR-12716
SockJsWebSocketHandler sockJs = (SockJsWebSocketHandler) wsHandler;
WebSocketServerSockJsSession session = (WebSocketServerSockJsSession) ReflectionTestUtils
.getField(sockJs, "sockJsSession");
this.attributes = session.getAttributes();
}
return true;
}
}
@Configuration
@EnableWebSocketSecurity
@EnableWebSocketMessageBroker
@Import(SyncExecutorConfig.class)
static class SockJsSecurityConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// @formatter:off
registry.addEndpoint("/other").setHandshakeHandler(testHandshakeHandler())
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
registry.addEndpoint("/chat").setHandshakeHandler(testHandshakeHandler())
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
// @formatter:on
}
// @formatter:off
@Bean
AuthorizationManager<Message<?>> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages,
SecurityCheck security) {
AuthorizationManager<MessageAuthorizationContext<?>> beanResolver =
(authentication, context) -> new AuthorizationDecision(security.check());
messages
.simpDestMatchers("/permitAll/**").permitAll()
.simpDestMatchers("/beanResolver/**").access(beanResolver)
.anyMessage().denyAll();
return messages.build();
}
// @formatter:on
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll");
}
@Bean
MyController myController() {
return new MyController();
}
@Bean
TestHandshakeHandler testHandshakeHandler() {
return new TestHandshakeHandler();
}
@Bean
SecurityCheck security() {
return new SecurityCheck();
}
static class SecurityCheck {
private boolean check;
boolean check() {
this.check = !this.check;
return this.check;
}
}
}
@Configuration
@EnableWebSocketSecurity
@EnableWebSocketMessageBroker
@Import(SyncExecutorConfig.class)
static class NoInboundSecurityConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// @formatter:off
registry.addEndpoint("/other")
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
registry.addEndpoint("/chat")
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
// @formatter:on
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll");
}
@Bean
MyController myController() {
return new MyController();
}
}
@Configuration
@Import(SockJsSecurityConfig.class)
static class CsrfDisabledSockJsSecurityConfig {
@Bean
Consumer<List<ChannelInterceptor>> channelInterceptorCustomizer() {
return (interceptors) -> interceptors.remove(1);
}
}
@Configuration
@EnableWebSocketSecurity
@EnableWebSocketMessageBroker
@Import(SyncExecutorConfig.class)
static class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// @formatter:off
registry.addEndpoint("/websocket")
.setHandshakeHandler(testHandshakeHandler())
.addInterceptors(new HttpSessionHandshakeInterceptor());
// @formatter:on
}
@Bean
AuthorizationManager<Message<?>> authorizationManager(
MessageMatcherDelegatingAuthorizationManager.Builder messages) {
// @formatter:off
messages
.simpDestMatchers("/permitAll/**").permitAll()
.anyMessage().denyAll();
// @formatter:on
return messages.build();
}
@Bean
TestHandshakeHandler testHandshakeHandler() {
return new TestHandshakeHandler();
}
}
@Configuration
@EnableWebSocketSecurity
@EnableWebSocketMessageBroker
@Import(SyncExecutorConfig.class)
static class UsingLegacyConfigurerConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// @formatter:off
registry.addEndpoint("/websocket")
.setHandshakeHandler(testHandshakeHandler())
.addInterceptors(new HttpSessionHandshakeInterceptor());
// @formatter:on
}
@Override
public void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
// @formatter:off
messages
.simpDestMatchers("/permitAll/**").permitAll()
.anyMessage().denyAll();
// @formatter:on
}
@Bean
TestHandshakeHandler testHandshakeHandler() {
return new TestHandshakeHandler();
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSocketSecurity
@EnableWebSocketMessageBroker
@Import(SyncExecutorConfig.class)
static class SockJsProxylessSecurityConfig implements WebSocketMessageBrokerConfigurer {
private ApplicationContext context;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// @formatter:off
registry.addEndpoint("/chat")
.setHandshakeHandler(this.context.getBean(TestHandshakeHandler.class))
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
// @formatter:on
}
@Autowired
void setContext(ApplicationContext context) {
this.context = context;
}
// @formatter:off
@Bean
AuthorizationManager<Message<?>> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
.anyMessage().denyAll();
return messages.build();
}
// @formatter:on
@Bean
TestHandshakeHandler testHandshakeHandler() {
return new TestHandshakeHandler();
}
}
@Configuration
static class SyncExecutorConfig {
@Bean
static SyncExecutorSubscribableChannelPostProcessor postProcessor() {
return new SyncExecutorSubscribableChannelPostProcessor();
}
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.config.websocket;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.assertj.core.api.ThrowableAssert;
import org.junit.jupiter.api.Test;
@@ -33,6 +34,8 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProce
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.MethodParameter;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -44,6 +47,8 @@ import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.expression.SecurityExpressionOperations;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
@@ -68,6 +73,9 @@ import org.springframework.web.socket.server.HandshakeHandler;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
/**
@@ -168,6 +176,78 @@ public class WebSocketMessageBrokerConfigTests {
send(message);
}
@Test
public void sendWhenNoIdSpecifiedThenIntegratesWithAuthorizationManager() {
this.spring.configLocations(xml("NoIdAuthorizationManager")).autowire();
this.clientInboundChannel.send(message("/permitAll"));
assertThatExceptionOfType(Exception.class).isThrownBy(() -> this.clientInboundChannel.send(message("/denyAll")))
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void sendWhenAnonymousMessageWithConnectMessageTypeThenAuthorizationManagerPermits() {
this.spring.configLocations(xml("NoIdAuthorizationManager")).autowire();
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
headers.setNativeHeader(this.token.getHeaderName(), this.token.getToken());
this.clientInboundChannel.send(message("/permitAll", headers));
}
@Test
public void sendWhenAnonymousMessageWithConnectAckMessageTypeThenAuthorizationManagerPermits() {
this.spring.configLocations(xml("NoIdAuthorizationManager")).autowire();
Message<?> message = message("/permitAll", SimpMessageType.CONNECT_ACK);
send(message);
}
@Test
public void sendWhenAnonymousMessageWithDisconnectMessageTypeThenAuthorizationManagerPermits() {
this.spring.configLocations(xml("NoIdAuthorizationManager")).autowire();
Message<?> message = message("/permitAll", SimpMessageType.DISCONNECT);
send(message);
}
@Test
public void sendWhenAnonymousMessageWithDisconnectAckMessageTypeThenAuthorizationManagerPermits() {
this.spring.configLocations(xml("NoIdAuthorizationManager")).autowire();
Message<?> message = message("/permitAll", SimpMessageType.DISCONNECT_ACK);
send(message);
}
@Test
public void sendWhenAnonymousMessageWithHeartbeatMessageTypeThenAuthorizationManagerPermits() {
this.spring.configLocations(xml("NoIdAuthorizationManager")).autowire();
Message<?> message = message("/permitAll", SimpMessageType.HEARTBEAT);
send(message);
}
@Test
public void sendWhenAnonymousMessageWithMessageMessageTypeThenAuthorizationManagerPermits() {
this.spring.configLocations(xml("NoIdAuthorizationManager")).autowire();
Message<?> message = message("/permitAll", SimpMessageType.MESSAGE);
send(message);
}
@Test
public void sendWhenAnonymousMessageWithOtherMessageTypeThenAuthorizationManagerPermits() {
this.spring.configLocations(xml("NoIdAuthorizationManager")).autowire();
Message<?> message = message("/permitAll", SimpMessageType.OTHER);
send(message);
}
@Test
public void sendWhenAnonymousMessageWithSubscribeMessageTypeThenAuthorizationManagerPermits() {
this.spring.configLocations(xml("NoIdAuthorizationManager")).autowire();
Message<?> message = message("/permitAll", SimpMessageType.SUBSCRIBE);
send(message);
}
@Test
public void sendWhenAnonymousMessageWithUnsubscribeMessageTypeThenAuthorizationManagerPermits() {
this.spring.configLocations(xml("NoIdAuthorizationManager")).autowire();
Message<?> message = message("/permitAll", SimpMessageType.UNSUBSCRIBE);
send(message);
}
@Test
public void sendWhenConnectWithoutCsrfTokenThenDenied() {
this.spring.configLocations(xml("SyncConfig")).autowire();
@@ -196,6 +276,19 @@ public class WebSocketMessageBrokerConfigTests {
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void sendWhenInterceptWiredForMessageTypeThenAuthorizationManagerDeniesOnTypeMismatch() {
this.spring.configLocations(xml("MessageInterceptTypeAuthorizationManager")).autowire();
Message<?> message = message("/permitAll", SimpMessageType.MESSAGE);
send(message);
message = message("/permitAll", SimpMessageType.UNSUBSCRIBE);
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
.withCauseInstanceOf(AccessDeniedException.class);
message = message("/anyOther", SimpMessageType.MESSAGE);
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void sendWhenInterceptWiredForSubscribeTypeThenDeniesOnTypeMismatch() {
this.spring.configLocations(xml("SubscribeInterceptTypeConfig")).autowire();
@@ -209,6 +302,19 @@ public class WebSocketMessageBrokerConfigTests {
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void sendWhenInterceptWiredForSubscribeTypeThenAuthorizationManagerDeniesOnTypeMismatch() {
this.spring.configLocations(xml("SubscribeInterceptTypeAuthorizationManager")).autowire();
Message<?> message = message("/permitAll", SimpMessageType.SUBSCRIBE);
send(message);
message = message("/permitAll", SimpMessageType.UNSUBSCRIBE);
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
.withCauseInstanceOf(AccessDeniedException.class);
message = message("/anyOther", SimpMessageType.SUBSCRIBE);
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void configureWhenUsingConnectMessageTypeThenAutowireFails() {
assertThatExceptionOfType(BeanDefinitionParsingException.class)
@@ -309,6 +415,16 @@ public class WebSocketMessageBrokerConfigTests {
send(message);
}
@Test
public void sendWhenUsingCustomPathMatcherThenAuthorizationManagerAppliesIt() {
this.spring.configLocations(xml("CustomPathMatcherAuthorizationManager")).autowire();
Message<?> message = message("/denyAll.a");
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
.withCauseInstanceOf(AccessDeniedException.class);
message = message("/denyAll.a.b");
send(message);
}
@Test
public void sendWhenIdSpecifiedThenSecurityDoesNotIntegrateWithClientInboundChannel() {
this.spring.configLocations(xml("IdConfig")).autowire();
@@ -342,6 +458,27 @@ public class WebSocketMessageBrokerConfigTests {
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
@WithMockUser(username = "nile")
public void sendWhenCustomExpressionHandlerThenAuthorizationManagerAuthorizesAccordingly() {
this.spring.configLocations(xml("CustomExpressionHandlerAuthorizationManager")).autowire();
Message<?> message = message("/denyNile");
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
.withCauseInstanceOf(AccessDeniedException.class);
}
@Test
public void sendWhenCustomAuthorizationManagerThenAuthorizesAccordingly() {
this.spring.configLocations(xml("CustomAuthorizationManagerConfig")).autowire();
AuthorizationManager<Message<?>> authorizationManager = this.spring.getContext()
.getBean(AuthorizationManager.class);
given(authorizationManager.check(any(), any())).willReturn(new AuthorizationDecision(false));
Message<?> message = message("/any");
assertThatExceptionOfType(Exception.class).isThrownBy(send(message))
.withCauseInstanceOf(AccessDeniedException.class);
verify(authorizationManager).check(any(), any());
}
private String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
@@ -466,6 +603,17 @@ public class WebSocketMessageBrokerConfigTests {
};
}
@Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication,
Message<Object> message) {
return new StandardEvaluationContext(new MessageSecurityExpressionRoot(authentication, message) {
public boolean denyNile() {
Authentication auth = getAuthentication();
return auth != null && !"nile".equals(auth.getName());
}
});
}
}
}
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<b:import resource="classpath:org/springframework/security/config/websocket/websocket.xml"/>
<b:bean name="authorizationManager" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.authorization.AuthorizationManager"/>
</b:bean>
<websocket-message-broker authorization-manager-ref="authorizationManager"/>
</b:beans>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<b:import resource="classpath:org/springframework/security/config/websocket/controllers.xml"/>
<b:import resource="classpath:org/springframework/security/config/websocket/websocket.xml"/>
<b:bean name="expressionHandler" class="org.springframework.security.messaging.access.expression.MessageAuthorizationContextSecurityExpressionHandler">
<b:constructor-arg>
<b:bean class="org.springframework.security.config.websocket.WebSocketMessageBrokerConfigTests.DenyNileMessageSecurityExpressionHandler"/>
</b:constructor-arg>
</b:bean>
<websocket-message-broker use-authorization-manager="true">
<expression-handler ref="expressionHandler"/>
<intercept-message pattern="/**" access="denyNile()"/>
</websocket-message-broker>
</b:beans>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<b:import resource="classpath:org/springframework/security/config/websocket/controllers.xml"/>
<websocket:message-broker path-matcher="pathMatcher">
<websocket:transport/>
<websocket:stomp-endpoint path="/app">
<websocket:handshake-handler ref="testHandler"/>
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/queue, /topic"/>
</websocket:message-broker>
<b:bean name="pathMatcher" class="org.springframework.util.AntPathMatcher">
<b:constructor-arg value="."/>
</b:bean>
<b:bean name="testHandler" class="org.springframework.security.config.websocket.WebSocketMessageBrokerConfigTests.TestHandshakeHandler"/>
<websocket-message-broker use-authorization-manager="true">
<intercept-message pattern="/denyAll.*" access="denyAll"/>
</websocket-message-broker>
</b:beans>
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<b:import resource="classpath:org/springframework/security/config/websocket/controllers.xml"/>
<b:import resource="classpath:org/springframework/security/config/websocket/websocket.xml"/>
<websocket-message-broker use-authorization-manager="true">
<intercept-message pattern="/permitAll" type="MESSAGE" access="permitAll"/>
<intercept-message pattern="/**" access="denyAll"/>
</websocket-message-broker>
</b:beans>
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<b:import resource="classpath:org/springframework/security/config/websocket/controllers.xml"/>
<b:import resource="classpath:org/springframework/security/config/websocket/websocket.xml"/>
<websocket-message-broker use-authorization-manager="true">
<intercept-message pattern="/permitAll" access="permitAll"/>
<intercept-message pattern="/denyAll" access="denyAll"/>
</websocket-message-broker>
</b:beans>
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<b:import resource="classpath:org/springframework/security/config/websocket/controllers.xml"/>
<b:import resource="classpath:org/springframework/security/config/websocket/websocket.xml"/>
<websocket-message-broker use-authorization-manager="true">
<intercept-message pattern="/permitAll" type="SUBSCRIBE" access="permitAll"/>
<intercept-message pattern="/**" access="denyAll"/>
</websocket-message-broker>
</b:beans>