SEC-2996: Suport configuring SecurityExpressionHandler<Message<Object>>

This commit is contained in:
Rob Winch 2015-07-13 17:23:02 -05:00
parent 3db01bd9d6
commit 64938ebcfc
10 changed files with 250 additions and 30 deletions

View File

@ -15,9 +15,17 @@
*/ */
package org.springframework.security.config.annotation.web.messaging; package org.springframework.security.config.annotation.web.messaging;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer; 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.expression.ExpressionBasedMessageSecurityMetadataSourceFactory;
import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource; import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource;
import org.springframework.security.messaging.util.matcher.MessageMatcher; import org.springframework.security.messaging.util.matcher.MessageMatcher;
@ -28,8 +36,6 @@ import org.springframework.util.Assert;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.util.*;
/** /**
* Allows mapping security constraints using {@link MessageMatcher} to the security * Allows mapping security constraints using {@link MessageMatcher} to the security
* expressions. * expressions.
@ -45,6 +51,8 @@ public class MessageSecurityMetadataSourceRegistry {
private static final String fullyAuthenticated = "fullyAuthenticated"; private static final String fullyAuthenticated = "fullyAuthenticated";
private static final String rememberMe = "rememberMe"; private static final String rememberMe = "rememberMe";
private SecurityExpressionHandler<Message<Object>> expressionHandler = new DefaultMessageSecurityExpressionHandler<Object>();
private final LinkedHashMap<MatcherBuilder, String> matcherToExpression = new LinkedHashMap<MatcherBuilder, String>(); private final LinkedHashMap<MatcherBuilder, String> matcherToExpression = new LinkedHashMap<MatcherBuilder, String>();
private DelegatingPathMatcher pathMatcher = new DelegatingPathMatcher(); private DelegatingPathMatcher pathMatcher = new DelegatingPathMatcher();
@ -200,6 +208,20 @@ public class MessageSecurityMetadataSourceRegistry {
return new Constraint(builders); return new Constraint(builders);
} }
/**
* The {@link SecurityExpressionHandler} to be used. The
* default is to use {@link DefaultMessageSecurityExpressionHandler}.
*
* @param expressionHandler the {@link SecurityExpressionHandler} to use. Cannot be null.
* @return the {@link MessageSecurityMetadataSourceRegistry} for further
* customization.
*/
public MessageSecurityMetadataSourceRegistry expressionHandler(SecurityExpressionHandler<Message<Object>> expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
return this;
}
/** /**
* Allows subclasses to create creating a {@link MessageSecurityMetadataSource}. * Allows subclasses to create creating a {@link MessageSecurityMetadataSource}.
* *
@ -217,7 +239,7 @@ public class MessageSecurityMetadataSourceRegistry {
matcherToExpression.put(entry.getKey().build(), entry.getValue()); matcherToExpression.put(entry.getKey().build(), entry.getValue());
} }
return ExpressionBasedMessageSecurityMetadataSourceFactory return ExpressionBasedMessageSecurityMetadataSourceFactory
.createExpressionMessageMetadataSource(matcherToExpression); .createExpressionMessageMetadataSource(matcherToExpression, expressionHandler);
} }
/** /**

View File

@ -26,10 +26,12 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler; import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.vote.AffirmativeBased; import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry; import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
import org.springframework.security.messaging.access.expression.MessageExpressionVoter; import org.springframework.security.messaging.access.expression.MessageExpressionVoter;
@ -80,6 +82,8 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends
AbstractWebSocketMessageBrokerConfigurer implements SmartInitializingSingleton { AbstractWebSocketMessageBrokerConfigurer implements SmartInitializingSingleton {
private final WebSocketMessageSecurityMetadataSourceRegistry inboundRegistry = new WebSocketMessageSecurityMetadataSourceRegistry(); private final WebSocketMessageSecurityMetadataSourceRegistry inboundRegistry = new WebSocketMessageSecurityMetadataSourceRegistry();
private SecurityExpressionHandler<Message<Object>> expressionHandler;
private ApplicationContext context; private ApplicationContext context;
public void registerStompEndpoints(StompEndpointRegistry registry) { public void registerStompEndpoints(StompEndpointRegistry registry) {
@ -145,8 +149,14 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends
public ChannelSecurityInterceptor inboundChannelSecurity() { public ChannelSecurityInterceptor inboundChannelSecurity() {
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor( ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor(
inboundMessageSecurityMetadataSource()); inboundMessageSecurityMetadataSource());
MessageExpressionVoter<Object> voter = new MessageExpressionVoter<Object>();
if(expressionHandler != null) {
voter.setExpressionHandler(expressionHandler);
}
List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<AccessDecisionVoter<? extends Object>>(); List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<AccessDecisionVoter<? extends Object>>();
voters.add(new MessageExpressionVoter<Object>()); voters.add(voter);
AffirmativeBased manager = new AffirmativeBased(voters); AffirmativeBased manager = new AffirmativeBased(voters);
channelSecurityInterceptor.setAccessDecisionManager(manager); channelSecurityInterceptor.setAccessDecisionManager(manager);
return channelSecurityInterceptor; return channelSecurityInterceptor;
@ -159,6 +169,9 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends
@Bean @Bean
public MessageSecurityMetadataSource inboundMessageSecurityMetadataSource() { public MessageSecurityMetadataSource inboundMessageSecurityMetadataSource() {
if(expressionHandler != null) {
inboundRegistry.expressionHandler(expressionHandler);
}
configureInbound(inboundRegistry); configureInbound(inboundRegistry);
return inboundRegistry.createMetadataSource(); return inboundRegistry.createMetadataSource();
} }
@ -193,6 +206,13 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends
this.context = context; this.context = context;
} }
@Autowired(required = false)
public void setMessageExpessionHandler(List<SecurityExpressionHandler<Message<Object>>> expressionHandlers) {
if(expressionHandlers.size() == 1) {
this.expressionHandler = expressionHandlers.get(0);
}
}
public void afterSingletonsInstantiated() { public void afterSingletonsInstantiated() {
if (sameOriginDisabled()) { if (sameOriginDisabled()) {
return; return;

View File

@ -15,6 +15,8 @@
*/ */
package org.springframework.security.config.websocket; package org.springframework.security.config.websocket;
import static org.springframework.security.config.Elements.*;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -117,6 +119,11 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
ManagedMap<BeanDefinition, String> matcherToExpression = new ManagedMap<BeanDefinition, String>(); ManagedMap<BeanDefinition, String> matcherToExpression = new ManagedMap<BeanDefinition, String>();
String id = element.getAttribute(ID_ATTR); String id = element.getAttribute(ID_ATTR);
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element,
EXPRESSION_HANDLER);
String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref");
boolean expressionHandlerDefined = StringUtils.hasText(expressionHandlerRef);
boolean sameOriginDisabled = Boolean.parseBoolean(element boolean sameOriginDisabled = Boolean.parseBoolean(element
.getAttribute(DISABLED_ATTR)); .getAttribute(DISABLED_ATTR));
@ -136,11 +143,18 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
.rootBeanDefinition(ExpressionBasedMessageSecurityMetadataSourceFactory.class); .rootBeanDefinition(ExpressionBasedMessageSecurityMetadataSourceFactory.class);
mds.setFactoryMethod("createExpressionMessageMetadataSource"); mds.setFactoryMethod("createExpressionMessageMetadataSource");
mds.addConstructorArgValue(matcherToExpression); mds.addConstructorArgValue(matcherToExpression);
if(expressionHandlerDefined) {
mds.addConstructorArgReference(expressionHandlerRef);
}
String mdsId = context.registerWithGeneratedName(mds.getBeanDefinition()); String mdsId = context.registerWithGeneratedName(mds.getBeanDefinition());
ManagedList<BeanDefinition> voters = new ManagedList<BeanDefinition>(); ManagedList<BeanDefinition> voters = new ManagedList<BeanDefinition>();
voters.add(new RootBeanDefinition(MessageExpressionVoter.class)); BeanDefinitionBuilder messageExpressionVoterBldr = BeanDefinitionBuilder.rootBeanDefinition(MessageExpressionVoter.class);
if(expressionHandlerDefined) {
messageExpressionVoterBldr.addPropertyReference("expressionHandler", expressionHandlerRef);
}
voters.add(messageExpressionVoterBldr.getBeanDefinition());
BeanDefinitionBuilder adm = BeanDefinitionBuilder BeanDefinitionBuilder adm = BeanDefinitionBuilder
.rootBeanDefinition(ConsensusBased.class); .rootBeanDefinition(ConsensusBased.class);
adm.addConstructorArgValue(voters); adm.addConstructorArgValue(voters);

View File

@ -274,7 +274,7 @@ protect-pointcut.attlist &=
websocket-message-broker = websocket-message-broker =
## Allows securing a Message Broker. There are two modes. If no id is 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. If the id is specified, creates a ChannelSecurityInterceptor that can be manually registered with the clientInboundChannel. ## Allows securing a Message Broker. There are two modes. If no id is 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. If the id is specified, creates a ChannelSecurityInterceptor that can be manually registered with the clientInboundChannel.
element websocket-message-broker { websocket-message-broker.attrlist, (intercept-message*) } element websocket-message-broker { websocket-message-broker.attrlist, (intercept-message* & expression-handler?) }
websocket-message-broker.attrlist &= 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. ## 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.

View File

@ -851,9 +851,20 @@
</xs:documentation> </xs:documentation>
</xs:annotation> </xs:annotation>
<xs:complexType> <xs:complexType>
<xs:sequence> <xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element minOccurs="0" maxOccurs="unbounded" ref="security:intercept-message"/> <xs:element ref="security:intercept-message"/>
</xs:sequence> <xs:element name="expression-handler">
<xs:annotation>
<xs:documentation>Defines the SecurityExpressionHandler instance which will be used if expression-based
access-control is enabled. A default implementation (with no ACL support) will be used if
not supplied.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="security:ref"/>
</xs:complexType>
</xs:element>
</xs:choice>
<xs:attributeGroup ref="security:websocket-message-broker.attrlist"/> <xs:attributeGroup ref="security:websocket-message-broker.attrlist"/>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@ -25,11 +25,14 @@ import org.springframework.messaging.support.GenericMessage
import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.access.AccessDeniedException import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.expression.SecurityExpressionOperations;
import org.springframework.security.authentication.TestingAuthenticationToken import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.AbstractXmlConfigTests import org.springframework.security.config.AbstractXmlConfigTests
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler;
import org.springframework.security.messaging.access.expression.MessageSecurityExpressionRoot;
import org.springframework.security.web.csrf.CsrfToken import org.springframework.security.web.csrf.CsrfToken
import org.springframework.security.web.csrf.DefaultCsrfToken import org.springframework.security.web.csrf.DefaultCsrfToken
import org.springframework.security.web.csrf.InvalidCsrfTokenException import org.springframework.security.web.csrf.InvalidCsrfTokenException
@ -432,6 +435,29 @@ class WebSocketMessageBrokerConfigTests extends AbstractXmlConfigTests {
createAppContext() createAppContext()
} }
def 'custom expressions'() {
setup:
bean('expressionHandler', DenyRobMessageSecurityExpressionHandler)
websocket {
'expression-handler' (ref: 'expressionHandler') {}
'intercept-message'(pattern:'/**',access:'denyRob()')
}
when: 'message is sent with user'
clientInboundChannel.send(message('/message'))
then: 'access is allowed to custom expression'
noExceptionThrown()
when:
messageUser = new TestingAuthenticationToken('rob', 'pass', 'ROLE_USER')
clientInboundChannel.send(message('/message'))
then:
def e = thrown(MessageDeliveryException)
e.cause instanceof AccessDeniedException
}
def getClientInboundChannel() { def getClientInboundChannel() {
appContext.getBean("clientInboundChannel") appContext.getBean("clientInboundChannel")
} }
@ -442,7 +468,6 @@ class WebSocketMessageBrokerConfigTests extends AbstractXmlConfigTests {
} }
def message(SimpMessageHeaderAccessor headers, String destination) { def message(SimpMessageHeaderAccessor headers, String destination) {
messageUser = new TestingAuthenticationToken('user','pass','ROLE_USER')
headers.sessionId = '123' headers.sessionId = '123'
headers.sessionAttributes = [:] headers.sessionAttributes = [:]
headers.destination = destination headers.destination = destination
@ -518,4 +543,18 @@ class WebSocketMessageBrokerConfigTests extends AbstractXmlConfigTests {
} }
} }
static class DenyRobMessageSecurityExpressionHandler extends DefaultMessageSecurityExpressionHandler<Object> {
@Override
protected SecurityExpressionOperations createSecurityExpressionRoot(
Authentication authentication,
Message<Object> invocation) {
return new MessageSecurityExpressionRoot(authentication, invocation) {
public boolean denyRob() {
Authentication auth = getAuthentication();
return auth != null && !"rob".equals(auth.getName());
}
};
}
}
} }

View File

@ -14,6 +14,14 @@
*/ */
package org.springframework.security.config.annotation.web.socket; package org.springframework.security.config.annotation.web.socket;
import static org.fest.assertions.Assertions.assertThat;
import static org.junit.Assert.fail;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -36,9 +44,14 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletConfig;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.expression.SecurityExpressionOperations;
import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry; 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.core.annotation.AuthenticationPrincipal;
import org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler;
import org.springframework.security.messaging.access.expression.MessageSecurityExpressionRoot;
import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken; import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.MissingCsrfTokenException; import org.springframework.security.web.csrf.MissingCsrfTokenException;
@ -57,14 +70,6 @@ import org.springframework.web.socket.server.support.HttpSessionHandshakeInterce
import org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler; import org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler;
import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession; import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import static org.fest.assertions.Assertions.assertThat;
import static org.junit.Assert.fail;
public class AbstractSecurityWebSocketMessageBrokerConfigurerTests { public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
AnnotationConfigWebApplicationContext context; AnnotationConfigWebApplicationContext context;
@ -389,6 +394,74 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
} }
} }
@Test
public void customExpression()
throws Exception {
loadConfig(CustomExpressionConfig.class);
clientInboundChannel().send(message("/denyRob"));
this.messageUser = new TestingAuthenticationToken("rob", "password", "ROLE_USER");
try {
clientInboundChannel().send(message("/denyRob"));
fail("Expected Exception");
}
catch (MessageDeliveryException expected) {
assertThat(expected.getCause()).isInstanceOf(AccessDeniedException.class);
}
}
@Configuration
@EnableWebSocketMessageBroker
@Import(SyncExecutorConfig.class)
static class CustomExpressionConfig extends
AbstractSecurityWebSocketMessageBrokerConfigurer {
// @formatter:off
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/other")
.setHandshakeHandler(testHandshakeHandler());
}
// @formatter:on
// @formatter:off
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.anyMessage().access("denyRob()");
}
// @formatter:on
@Bean
public SecurityExpressionHandler<Message<Object>> messageSecurityExpressionHandler() {
return new DefaultMessageSecurityExpressionHandler<Object>() {
@Override
protected SecurityExpressionOperations createSecurityExpressionRoot(
Authentication authentication,
Message<Object> invocation) {
return new MessageSecurityExpressionRoot(authentication, invocation) {
public boolean denyRob() {
Authentication auth = getAuthentication();
return auth != null && !"rob".equals(auth.getName());
}
};
}
};
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
@Bean
public TestHandshakeHandler testHandshakeHandler() {
return new TestHandshakeHandler();
}
}
private void assertHandshake(HttpServletRequest request) { private void assertHandshake(HttpServletRequest request) {
TestHandshakeHandler handshakeHandler = context TestHandshakeHandler handshakeHandler = context
.getBean(TestHandshakeHandler.class); .getBean(TestHandshakeHandler.class);
@ -597,6 +670,7 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages messages
.simpDestMatchers("/permitAll/**").permitAll() .simpDestMatchers("/permitAll/**").permitAll()
.simpDestMatchers("/customExpression/**").access("denyRob")
.anyMessage().denyAll(); .anyMessage().denyAll();
} }
// @formatter:on // @formatter:on

View File

@ -7260,6 +7260,7 @@ Defines the `SecurityExpressionHandler` instance which will be used if expressio
* <<nsa-global-method-security,global-method-security>> * <<nsa-global-method-security,global-method-security>>
* <<nsa-http,http>> * <<nsa-http,http>>
* <<nsa-websocket-message-broker,websocket-message-broker>>
@ -8030,6 +8031,7 @@ If additional control is necessary, the id can be specified and a ChannelSecurit
===== Child Elements of <websocket-message-broker> ===== Child Elements of <websocket-message-broker>
* <<nsa-expression-handler,expression-handler>>
* <<nsa-intercept-message,intercept-message>> * <<nsa-intercept-message,intercept-message>>
[[nsa-intercept-message]] [[nsa-intercept-message]]

View File

@ -15,17 +15,19 @@
*/ */
package org.springframework.security.messaging.access.expression; package org.springframework.security.messaging.access.expression;
import org.springframework.expression.Expression;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.messaging.access.intercept.DefaultMessageSecurityMetadataSource;
import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource;
import org.springframework.security.messaging.util.matcher.MessageMatcher;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.expression.Expression;
import org.springframework.messaging.Message;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.messaging.access.intercept.DefaultMessageSecurityMetadataSource;
import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource;
import org.springframework.security.messaging.util.matcher.MessageMatcher;
/** /**
* A class used to create a {@link MessageSecurityMetadataSource} that uses * A class used to create a {@link MessageSecurityMetadataSource} that uses
* {@link MessageMatcher} mapped to Spring Expressions. * {@link MessageMatcher} mapped to Spring Expressions.
@ -68,7 +70,43 @@ public final class ExpressionBasedMessageSecurityMetadataSourceFactory {
*/ */
public static MessageSecurityMetadataSource createExpressionMessageMetadataSource( public static MessageSecurityMetadataSource createExpressionMessageMetadataSource(
LinkedHashMap<MessageMatcher<?>, String> matcherToExpression) { LinkedHashMap<MessageMatcher<?>, String> matcherToExpression) {
DefaultMessageSecurityExpressionHandler<Object> handler = new DefaultMessageSecurityExpressionHandler<Object>(); return createExpressionMessageMetadataSource(matcherToExpression, new DefaultMessageSecurityExpressionHandler<Object>());
}
/**
* Create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher}
* mapped to Spring Expressions. Each entry is considered in order and only the first
* match is used.
*
* For example:
*
* <pre>
* LinkedHashMap<MessageMatcher<?> matcherToExpression = new LinkedHashMap<MessageMatcher<Object>();
* matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
* matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
* matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
*
* MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
* </pre>
*
* <p>
* If our destination is "/public/hello", it would match on "/public/**" and on "/**".
* However, only "/public/**" would be used since it is the first entry. That means
* that a destination of "/public/hello" will be mapped to "permitAll".
* </p>
*
* <p>
* For a complete listing of expressions see {@link MessageSecurityExpressionRoot}
* </p>
*
* @param matcherToExpression an ordered mapping of {@link MessageMatcher} to Strings
* that are turned into an Expression using
* {@link DefaultMessageSecurityExpressionHandler#getExpressionParser()}
* @param handler the {@link SecurityExpressionHandler} to use
* @return the {@link MessageSecurityMetadataSource} to use. Cannot be null.
*/
public static MessageSecurityMetadataSource createExpressionMessageMetadataSource(
LinkedHashMap<MessageMatcher<?>, String> matcherToExpression, SecurityExpressionHandler<Message<Object>> handler) {
LinkedHashMap<MessageMatcher<?>, Collection<ConfigAttribute>> matcherToAttrs = new LinkedHashMap<MessageMatcher<?>, Collection<ConfigAttribute>>(); LinkedHashMap<MessageMatcher<?>, Collection<ConfigAttribute>> matcherToAttrs = new LinkedHashMap<MessageMatcher<?>, Collection<ConfigAttribute>>();

View File

@ -25,7 +25,7 @@ import org.springframework.security.core.Authentication;
* @since 4.0 * @since 4.0
* @author Rob Winch * @author Rob Winch
*/ */
public final class MessageSecurityExpressionRoot extends SecurityExpressionRoot { public class MessageSecurityExpressionRoot extends SecurityExpressionRoot {
public final Message<?> message; public final Message<?> message;