diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/messaging/MessageSecurityMetadataSourceRegistry.java b/config/src/main/java/org/springframework/security/config/annotation/web/messaging/MessageSecurityMetadataSourceRegistry.java index 4242b5c9d3..4bf9de75a3 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/messaging/MessageSecurityMetadataSourceRegistry.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/messaging/MessageSecurityMetadataSourceRegistry.java @@ -15,9 +15,17 @@ */ 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.simp.SimpMessageType; +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.MessageSecurityMetadataSource; 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.StringUtils; -import java.util.*; - /** * Allows mapping security constraints using {@link MessageMatcher} to the security * expressions. @@ -45,6 +51,8 @@ public class MessageSecurityMetadataSourceRegistry { private static final String fullyAuthenticated = "fullyAuthenticated"; private static final String rememberMe = "rememberMe"; + private SecurityExpressionHandler> expressionHandler = new DefaultMessageSecurityExpressionHandler(); + private final LinkedHashMap matcherToExpression = new LinkedHashMap(); private DelegatingPathMatcher pathMatcher = new DelegatingPathMatcher(); @@ -200,6 +208,20 @@ public class MessageSecurityMetadataSourceRegistry { 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> expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.expressionHandler = expressionHandler; + return this; + } + /** * Allows subclasses to create creating a {@link MessageSecurityMetadataSource}. * @@ -217,7 +239,7 @@ public class MessageSecurityMetadataSourceRegistry { matcherToExpression.put(entry.getKey().build(), entry.getValue()); } return ExpressionBasedMessageSecurityMetadataSourceFactory - .createExpressionMessageMetadataSource(matcherToExpression); + .createExpressionMessageMetadataSource(matcherToExpression, expressionHandler); } /** 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 2bb5e42582..be4a693ea2 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 @@ -26,10 +26,12 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; 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.annotation.support.SimpAnnotationMethodMessageHandler; import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.security.access.AccessDecisionVoter; +import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.access.vote.AffirmativeBased; import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry; import org.springframework.security.messaging.access.expression.MessageExpressionVoter; @@ -80,6 +82,8 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends AbstractWebSocketMessageBrokerConfigurer implements SmartInitializingSingleton { private final WebSocketMessageSecurityMetadataSourceRegistry inboundRegistry = new WebSocketMessageSecurityMetadataSourceRegistry(); + private SecurityExpressionHandler> expressionHandler; + private ApplicationContext context; public void registerStompEndpoints(StompEndpointRegistry registry) { @@ -145,8 +149,14 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends public ChannelSecurityInterceptor inboundChannelSecurity() { ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor( inboundMessageSecurityMetadataSource()); + MessageExpressionVoter voter = new MessageExpressionVoter(); + if(expressionHandler != null) { + voter.setExpressionHandler(expressionHandler); + } + List> voters = new ArrayList>(); - voters.add(new MessageExpressionVoter()); + voters.add(voter); + AffirmativeBased manager = new AffirmativeBased(voters); channelSecurityInterceptor.setAccessDecisionManager(manager); return channelSecurityInterceptor; @@ -159,6 +169,9 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends @Bean public MessageSecurityMetadataSource inboundMessageSecurityMetadataSource() { + if(expressionHandler != null) { + inboundRegistry.expressionHandler(expressionHandler); + } configureInbound(inboundRegistry); return inboundRegistry.createMetadataSource(); } @@ -193,6 +206,13 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends this.context = context; } + @Autowired(required = false) + public void setMessageExpessionHandler(List>> expressionHandlers) { + if(expressionHandlers.size() == 1) { + this.expressionHandler = expressionHandlers.get(0); + } + } + public void afterSingletonsInstantiated() { 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 ef835a9981..9fdd983608 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 @@ -15,6 +15,8 @@ */ package org.springframework.security.config.websocket; +import static org.springframework.security.config.Elements.*; + import java.util.Comparator; import java.util.List; import java.util.Map; @@ -117,6 +119,11 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements ManagedMap matcherToExpression = new ManagedMap(); 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 .getAttribute(DISABLED_ATTR)); @@ -136,11 +143,18 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements .rootBeanDefinition(ExpressionBasedMessageSecurityMetadataSourceFactory.class); mds.setFactoryMethod("createExpressionMessageMetadataSource"); mds.addConstructorArgValue(matcherToExpression); + if(expressionHandlerDefined) { + mds.addConstructorArgReference(expressionHandlerRef); + } String mdsId = context.registerWithGeneratedName(mds.getBeanDefinition()); ManagedList voters = new ManagedList(); - 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 .rootBeanDefinition(ConsensusBased.class); adm.addConstructorArgValue(voters); 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 df0dd01b84..a92fee59fc 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 @@ -274,7 +274,7 @@ protect-pointcut.attlist &= 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. - 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 &= ## 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. @@ -291,10 +291,10 @@ intercept-message.attrlist &= ## The destination ant pattern which will be mapped to the access attribute. For example, /** matches any message with a destination, /admin/** matches any message that has a destination that starts with admin. attribute pattern {xsd:token}? intercept-message.attrlist &= - ## The access configuration attributes that apply for the configured message. For example, permitAll grants access to anyone, hasRole('ROLE_ADMIN') requires the user have the role 'ROLE_ADMIN'. + ## The access configuration attributes that apply for the configured message. For example, permitAll grants access to anyone, hasRole('ROLE_ADMIN') requires the user have the role 'ROLE_ADMIN'. attribute access {xsd:token}? intercept-message.attrlist &= - ## The type of message to match on. Valid values are defined in SimpMessageType (i.e. CONNECT, CONNECT_ACK, HEARTBEAT, MESSAGE, SUBSCRIBE, UNSUBSCRIBE, DISCONNECT, DISCONNECT_ACK, OTHER). + ## The type of message to match on. Valid values are defined in SimpMessageType (i.e. CONNECT, CONNECT_ACK, HEARTBEAT, MESSAGE, SUBSCRIBE, UNSUBSCRIBE, DISCONNECT, DISCONNECT_ACK, OTHER). attribute type {"CONNECT" | "CONNECT_ACK" | "HEARTBEAT" | "MESSAGE" | "SUBSCRIBE"| "UNSUBSCRIBE" | "DISCONNECT" | "DISCONNECT_ACK" | "OTHER"}? http-firewall = @@ -704,7 +704,7 @@ user.attlist &= ## Can be set to "true" to mark an account as locked and unusable. attribute locked {xsd:boolean}? user.attlist &= - ## Can be set to "true" to mark an account as disabled and unusable. + ## Can be set to "true" to mark an account as disabled and unusable. attribute disabled {xsd:boolean}? jdbc-user-service = 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 5935e76c88..31f3977584 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 @@ -851,9 +851,20 @@ - - - + + + + + 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. + + + + + + + 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 e02d3aeeb9..b22300a547 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 @@ -25,11 +25,14 @@ 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.access.expression.SecurityExpressionOperations; 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.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.DefaultCsrfToken import org.springframework.security.web.csrf.InvalidCsrfTokenException @@ -432,6 +435,29 @@ class WebSocketMessageBrokerConfigTests extends AbstractXmlConfigTests { 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() { appContext.getBean("clientInboundChannel") } @@ -442,7 +468,6 @@ class WebSocketMessageBrokerConfigTests extends AbstractXmlConfigTests { } def message(SimpMessageHeaderAccessor headers, String destination) { - messageUser = new TestingAuthenticationToken('user','pass','ROLE_USER') headers.sessionId = '123' headers.sessionAttributes = [:] headers.destination = destination @@ -518,4 +543,18 @@ class WebSocketMessageBrokerConfigTests extends AbstractXmlConfigTests { } } + + static class DenyRobMessageSecurityExpressionHandler extends DefaultMessageSecurityExpressionHandler { + @Override + protected SecurityExpressionOperations createSecurityExpressionRoot( + Authentication authentication, + Message invocation) { + return new MessageSecurityExpressionRoot(authentication, invocation) { + public boolean denyRob() { + Authentication auth = getAuthentication(); + return auth != null && !"rob".equals(auth.getName()); + } + }; + } + } } 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 3c366131f5..7daa3b6204 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 @@ -14,6 +14,14 @@ */ 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.Before; 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.MockServletConfig; 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.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry; +import org.springframework.security.core.Authentication; 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.DefaultCsrfToken; 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.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 { 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> messageSecurityExpressionHandler() { + return new DefaultMessageSecurityExpressionHandler() { + @Override + protected SecurityExpressionOperations createSecurityExpressionRoot( + Authentication authentication, + Message 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) { TestHandshakeHandler handshakeHandler = context .getBean(TestHandshakeHandler.class); @@ -597,6 +670,7 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests { protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { messages .simpDestMatchers("/permitAll/**").permitAll() + .simpDestMatchers("/customExpression/**").access("denyRob") .anyMessage().denyAll(); } // @formatter:on diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index d8591180b6..e06bf6ec8d 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -7260,6 +7260,7 @@ Defines the `SecurityExpressionHandler` instance which will be used if expressio * <> * <> +* <> @@ -8030,6 +8031,7 @@ If additional control is necessary, the id can be specified and a ChannelSecurit ===== Child Elements of +* <> * <> [[nsa-intercept-message]] diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java index 6573c5a367..2fa7b0d8d9 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java +++ b/messaging/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java @@ -15,17 +15,19 @@ */ package org.springframework.security.messaging.access.expression; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +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; -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; - /** * A class used to create a {@link MessageSecurityMetadataSource} that uses * {@link MessageMatcher} mapped to Spring Expressions. @@ -47,7 +49,7 @@ public final class ExpressionBasedMessageSecurityMetadataSourceFactory { * matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll"); * matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')"); * matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated"); - * + * * MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression); * * @@ -68,7 +70,43 @@ public final class ExpressionBasedMessageSecurityMetadataSourceFactory { */ public static MessageSecurityMetadataSource createExpressionMessageMetadataSource( LinkedHashMap, String> matcherToExpression) { - DefaultMessageSecurityExpressionHandler handler = new DefaultMessageSecurityExpressionHandler(); + return createExpressionMessageMetadataSource(matcherToExpression, new DefaultMessageSecurityExpressionHandler()); + } + + /** + * 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: + * + *
+	 *     LinkedHashMap matcherToExpression = new LinkedHashMap();
+	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
+	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
+	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
+	 *
+	 *     MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
+	 * 
+ * + *

+ * 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". + *

+ * + *

+ * For a complete listing of expressions see {@link MessageSecurityExpressionRoot} + *

+ * + * @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, String> matcherToExpression, SecurityExpressionHandler> handler) { LinkedHashMap, Collection> matcherToAttrs = new LinkedHashMap, Collection>(); diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java index 3f209e8293..282bf12309 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java +++ b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java @@ -25,7 +25,7 @@ import org.springframework.security.core.Authentication; * @since 4.0 * @author Rob Winch */ -public final class MessageSecurityExpressionRoot extends SecurityExpressionRoot { +public class MessageSecurityExpressionRoot extends SecurityExpressionRoot { public final Message message;