mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-05 10:12:36 +00:00
SEC-2996: Suport configuring SecurityExpressionHandler<Message<Object>>
This commit is contained in:
parent
3db01bd9d6
commit
64938ebcfc
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
|
@ -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>
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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]]
|
||||||
|
@ -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>>();
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user