diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.java index 3335a53aca..54d1e99a01 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.java @@ -88,7 +88,7 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends A public final void configureClientInboundChannel(ChannelRegistration registration) { ChannelSecurityInterceptor inboundChannelSecurity = inboundChannelSecurity(); registration.setInterceptors(securityContextChannelInterceptor()); - if(sameOriginEnforced()) { + if(!sameOriginDisabled()) { registration.setInterceptors(csrfChannelInterceptor()); } if(inboundRegistry.containsMapping()) { @@ -100,16 +100,16 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends A /** *

* Determines if a CSRF token is required for connecting. This protects against remote sites from connecting to the - * application and being able to read/write data over the connection. The default is true. + * application and being able to read/write data over the connection. The default is false (the token is required). *

*

* Subclasses can override this method to disable CSRF protection *

* - * @return true if a CSRF is required for connecting, else false + * @return false if a CSRF token is required for connecting, else true */ - protected boolean sameOriginEnforced() { - return true; + protected boolean sameOriginDisabled() { + return false; } /** @@ -170,7 +170,7 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends A } public void afterSingletonsInstantiated() { - if(!sameOriginEnforced()) { + if(sameOriginDisabled()) { return; } diff --git a/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java index 00e0169100..c9142628fb 100644 --- a/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/websocket/WebSocketMessageBrokerSecurityBeanDefinitionParser.java @@ -86,6 +86,8 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements private static final String ID_ATTR = "id"; + private static final String DISABLED_ATTR = "same-origin-disabled"; + private static final String PATTERN_ATTR = "pattern"; private static final String ACCESS_ATTR = "access"; @@ -105,6 +107,8 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements ManagedMap matcherToExpression = new ManagedMap(); String id = element.getAttribute(ID_ATTR); + boolean sameOriginDisabled = Boolean.parseBoolean(element.getAttribute(DISABLED_ATTR)); + List interceptMessages = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_MESSAGE); for(Element interceptMessage : interceptMessages) { @@ -137,6 +141,7 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements } else { BeanDefinitionBuilder mspp = BeanDefinitionBuilder.rootBeanDefinition(MessageSecurityPostProcessor.class); mspp.addConstructorArgValue(inSecurityInterceptorName); + mspp.addConstructorArgValue(sameOriginDisabled); context.registerWithGeneratedName(mspp.getBeanDefinition()); } @@ -180,8 +185,11 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements private final String inboundSecurityInterceptorId; - public MessageSecurityPostProcessor(String inboundSecurityInterceptorId) { + private final boolean sameOriginDisabled; + + public MessageSecurityPostProcessor(String inboundSecurityInterceptorId, boolean sameOriginDisabled) { this.inboundSecurityInterceptorId = inboundSecurityInterceptorId; + this.sameOriginDisabled = sameOriginDisabled; } public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { @@ -212,7 +220,9 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements } ManagedList interceptors = new ManagedList(); interceptors.add(new RootBeanDefinition(SecurityContextChannelInterceptor.class)); - interceptors.add(new RootBeanDefinition(CsrfChannelInterceptor.class)); + if(!sameOriginDisabled) { + interceptors.add(new RootBeanDefinition(CsrfChannelInterceptor.class)); + } interceptors.add(registry.getBeanDefinition(inboundSecurityInterceptorId)); BeanDefinition inboundChannel = registry.getBeanDefinition(CLIENT_INBOUND_CHANNEL_BEAN_ID); @@ -226,6 +236,9 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements } private void addCsrfTokenHandshakeInterceptor(BeanDefinition bd) { + if(sameOriginDisabled) { + return; + } String interceptorPropertyName = "handshakeInterceptors"; ManagedList interceptors = new ManagedList(); interceptors.add(new RootBeanDefinition(CsrfTokenHandshakeInterceptor.class)); diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-4.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-4.0.rnc index e1671b6922..44657d7479 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-4.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-4.0.rnc @@ -279,6 +279,9 @@ websocket-message-broker = websocket-message-broker.attrlist &= ## A bean identifier, used for referring to the bean elsewhere in the context. If specified, explicit configuration within clientInboundChannel is required. If not specified, ensures that any SimpAnnotationMethodMessageHandler has the AuthenticationPrincipalArgumentResolver registered as a custom argument resolver; ensures that the SecurityContextChannelInterceptor is automatically registered for the clientInboundChannel; and that a ChannelSecurityInterceptor is registered with the clientInboundChannel. attribute id {xsd:token}? +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}? intercept-message = ## Creates an authorization rule for a websocket message. diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-4.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-4.0.xsd index fd21d91486..e8a014d829 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-4.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-4.0.xsd @@ -870,6 +870,14 @@ + + + Disables the requrement 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. + + + diff --git a/config/src/test/groovy/org/springframework/security/config/websocket/WebSocketMessageBrokerConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/websocket/WebSocketMessageBrokerConfigTests.groovy index 7d41bd199d..36b497ce02 100644 --- a/config/src/test/groovy/org/springframework/security/config/websocket/WebSocketMessageBrokerConfigTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/websocket/WebSocketMessageBrokerConfigTests.groovy @@ -263,6 +263,24 @@ class WebSocketMessageBrokerConfigTests extends AbstractXmlConfigTests { expected.cause instanceof InvalidCsrfTokenException } + def 'messages of type CONNECT disabled valid CsrfToken'() { + setup: + def id = 'authenticationController' + bean(id,MyController) + bean('inPostProcessor',InboundExecutorPostProcessor) + websocket('same-origin-disabled':true) { + 'intercept-message'(pattern:'/**',access:'permitAll') + } + + when: 'websocket of type CONNECTION is sent without CsrfTOken' + SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT) + Message message = message(headers,'/authentication') + clientInboundChannel.send(message) + + then: 'CSRF Protection blocks the Message' + noExceptionThrown() + } + def 'websocket with no id does not override customArgumentResolvers'() { setup: def id = 'authenticationController' diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurerTests.java index f2c16d703a..00070de16d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurerTests.java @@ -408,8 +408,8 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests { static class CsrfDisabledSockJsSecurityConfig extends SockJsSecurityConfig { @Override - protected boolean sameOriginEnforced() { - return false; + protected boolean sameOriginDisabled() { + return true; } } diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index 360e765507..55dc0d8521 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -7815,6 +7815,9 @@ If additional control is necessary, the id can be specified and a ChannelSecurit [[nsa-websocket-message-broker-id]] * **id** A bean identifier, used for referring to the ChannelSecurityInterceptor bean elsewhere in the context. If specified, Spring Security requires explicit configuration within Spring Messaging. If not specified, Spring Security will automatically integrate with the messaging infrastructure as described in <> +[[nsa-websocket-message-broker-same-origin-disabled]] +* **same-origin-disabled** 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. + [[nsa-websocket-message-broker-children]] ===== Child Elements of