SEC-2864: Default Spring Security WebSocket PathMatcher XML Namespace

This commit is contained in:
Rob Winch 2015-03-25 16:32:03 -05:00
parent db531d9100
commit 7b25b3e40d
2 changed files with 542 additions and 455 deletions

View File

@ -38,11 +38,14 @@ import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatche
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.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Parses Spring Security's websocket namespace support. A simple example is:
@ -84,9 +87,6 @@ import java.util.List;
*/
public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
BeanDefinitionParser {
private static final Log logger = LogFactory
.getLog(WebSocketMessageBrokerSecurityBeanDefinitionParser.class);
private static final String ID_ATTR = "id";
private static final String DISABLED_ATTR = "same-origin-disabled";
@ -97,6 +97,8 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
private static final String TYPE_ATTR = "type";
private static final String PATH_MATCHER_BEAN_NAME = "springSecurityMessagePathMatcher";
/**
* @param element
* @param parserContext
@ -149,6 +151,10 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
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
@ -190,16 +196,18 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
interceptMessage);
}
}
BeanDefinitionBuilder matcher = BeanDefinitionBuilder
.rootBeanDefinition(SimpDestinationMessageMatcher.class);
matcher.setFactoryMethod(factoryName);
matcher.addConstructorArgValue(matcherPattern);
matcher.addConstructorArgValue(new RootBeanDefinition(AntPathMatcher.class));
matcher.addConstructorArgValue(new RuntimeBeanReference("springSecurityMessagePathMatcher"));
return matcher.getBeanDefinition();
}
static class MessageSecurityPostProcessor implements
BeanDefinitionRegistryPostProcessor {
private static final String CLIENT_INBOUND_CHANNEL_BEAN_ID = "clientInboundChannel";
private static final String INTERCEPTORS_PROP = "interceptors";
@ -233,6 +241,14 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
argResolvers.add(new RootBeanDefinition(
AuthenticationPrincipalArgumentResolver.class));
bd.getPropertyValues().add(CUSTOM_ARG_RESOLVERS_PROP, argResolvers);
if(!registry.containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
PropertyValue pathMatcherProp = bd.getPropertyValues().getPropertyValue("pathMatcher");
Object pathMatcher = pathMatcherProp == null ? null : pathMatcherProp.getValue();
if(pathMatcher instanceof BeanReference) {
registry.registerAlias(((BeanReference) pathMatcher).getBeanName(), PATH_MATCHER_BEAN_NAME);
}
}
}
else if (beanClassName
.equals("org.springframework.web.socket.server.support.WebSocketHttpRequestHandler")) {
@ -270,6 +286,10 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
}
inboundChannel.getPropertyValues().add(INTERCEPTORS_PROP, interceptors);
if(!registry.containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
registry.registerBeanDefinition(PATH_MATCHER_BEAN_NAME, new RootBeanDefinition(AntPathMatcher.class));
}
}
private void addCsrfTokenHandshakeInterceptor(BeanDefinition bd) {
@ -289,4 +309,41 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
}
}
static class DelegatingPathMatcher implements PathMatcher {
private PathMatcher delegate = new AntPathMatcher();
public boolean isPattern(String path) {
return delegate.isPattern(path);
}
public boolean match(String pattern, String path) {
return delegate.match(pattern, path);
}
public boolean matchStart(String pattern, String path) {
return delegate.matchStart(pattern, path);
}
public String extractPathWithinPattern(String pattern, String path) {
return delegate.extractPathWithinPattern(pattern, path);
}
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
return delegate.extractUriTemplateVariables(pattern, path);
}
public Comparator<String> getPatternComparator(String path) {
return delegate.getPatternComparator(path);
}
public String combine(String pattern1, String pattern2) {
return delegate.combine(pattern1, pattern2);
}
void setPathMatcher(PathMatcher pathMatcher) {
this.delegate = pathMatcher;
}
}
}

View File

@ -1,5 +1,7 @@
package org.springframework.security.config.websocket
import static org.mockito.Mockito.*
import org.springframework.beans.BeansException
import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
@ -11,21 +13,29 @@ import org.springframework.core.MethodParameter
import org.springframework.core.task.SyncTaskExecutor
import org.springframework.http.server.ServerHttpRequest
import org.springframework.http.server.ServerHttpResponse
import org.springframework.messaging.Message
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.annotation.support.SimpAnnotationMethodMessageHandler
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.security.access.AccessDeniedException
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.AbstractXmlConfigTests
import org.springframework.security.core.Authentication
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.web.csrf.CsrfToken
import org.springframework.security.web.csrf.DefaultCsrfToken
import org.springframework.security.web.csrf.InvalidCsrfTokenException
import org.springframework.security.web.csrf.MissingCsrfTokenException
import org.springframework.stereotype.Controller
import org.springframework.util.AntPathMatcher
import org.springframework.web.servlet.HandlerMapping
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
import org.springframework.web.socket.WebSocketHandler
import org.springframework.web.socket.server.HandshakeFailureException
import org.springframework.web.socket.server.HandshakeHandler
@ -33,20 +43,9 @@ import org.springframework.web.socket.server.support.HttpSessionHandshakeInterce
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler
import org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler
import spock.lang.Unroll
import static org.mockito.Mockito.*
import org.springframework.messaging.Message
import org.springframework.messaging.MessageDeliveryException
import org.springframework.messaging.simp.SimpMessageHeaderAccessor
import org.springframework.messaging.support.ChannelInterceptor
import org.springframework.messaging.support.GenericMessage
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.AbstractXmlConfigTests
import org.springframework.security.core.context.SecurityContextHolder
/**
*
* @author Rob Winch
@ -309,6 +308,37 @@ class WebSocketMessageBrokerConfigTests extends AbstractXmlConfigTests {
controller.myCustomArgument!= null
}
def 'websocket defaults pathMatcher'() {
setup:
bean('pathMatcher',AntPathMatcher.name,['.'])
bean('testHandler', TestHandshakeHandler)
xml.'websocket:message-broker'('path-matcher':'pathMatcher') {
'websocket:transport' {}
'websocket:stomp-endpoint'(path:'/app') {
'websocket:handshake-handler'(ref:'testHandler') {}
}
'websocket:simple-broker'(prefix:"/queue, /topic"){}
}
xml.'websocket-message-broker' {
'intercept-message'(pattern:'/denyAll.*',access:'denyAll')
}
createAppContext()
when: 'sent to denyAll.a'
appContext.getBean(SimpAnnotationMethodMessageHandler)
clientInboundChannel.send(message('/denyAll.a'))
then: 'access is denied'
MessageDeliveryException expected = thrown()
expected.cause instanceof AccessDeniedException
when: 'sent to denyAll.a.b'
clientInboundChannel.send(message('/denyAll.a.b'))
then: 'access is allowed'
noExceptionThrown()
}
def 'websocket with id does not integrate with clientInboundChannel'() {
setup:
websocket([id:'inCsi']) {