Add AuthorizationManager to Messaging
Closes gh-11076
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ 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.MessageMatcherDelegatingAuthorizationManager;
|
||||
import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource;
|
||||
import org.springframework.security.messaging.util.matcher.MessageMatcher;
|
||||
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
|
||||
@@ -43,7 +44,9 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 4.0
|
||||
* @deprecated Use {@link MessageMatcherDelegatingAuthorizationManager} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class MessageSecurityMetadataSourceRegistry {
|
||||
|
||||
private static final String permitAll = "permitAll";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -81,9 +81,12 @@ import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsSe
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 4.0
|
||||
* @see WebSocketMessageBrokerSecurityConfiguration
|
||||
* @deprecated Use {@link EnableWebSocketSecurity} instead
|
||||
*/
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
|
||||
@Import(ObjectPostProcessorConfiguration.class)
|
||||
@Deprecated
|
||||
public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer
|
||||
implements WebSocketMessageBrokerConfigurer, SmartInitializingSingleton {
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.socket;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Allows configuring WebSocket Authorization.
|
||||
*
|
||||
* <p>
|
||||
* For example:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* @Configuration
|
||||
* @EnableWebSocketSecurity
|
||||
* public class WebSocketSecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* AuthorizationManager<Message<?>> (MessageMatcherDelegatingAuthorizationManager.Builder messages) {
|
||||
* messages.simpDestMatchers("/user/queue/errors").permitAll()
|
||||
* .simpDestMatchers("/admin/**").hasRole("ADMIN")
|
||||
* .anyMessage().authenticated();
|
||||
* return messages.build();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.8
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Import(WebSocketMessageBrokerSecurityConfiguration.class)
|
||||
public @interface EnableWebSocketSecurity {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.socket;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
|
||||
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
final class MessageMatcherAuthorizationManagerConfiguration {
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
MessageMatcherDelegatingAuthorizationManager.Builder messageAuthorizationManagerBuilder(
|
||||
ApplicationContext context) {
|
||||
return MessageMatcherDelegatingAuthorizationManager.builder().simpDestPathMatcher(
|
||||
() -> (context.getBeanNamesForType(SimpAnnotationMethodMessageHandler.class).length > 0)
|
||||
? context.getBean(SimpAnnotationMethodMessageHandler.class).getPathMatcher()
|
||||
: new AntPathMatcher());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.socket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Import;
|
||||
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.config.ChannelRegistration;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
|
||||
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
|
||||
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
|
||||
import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
|
||||
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
|
||||
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
|
||||
import org.springframework.security.messaging.web.socket.server.CsrfTokenHandshakeInterceptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsService;
|
||||
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService;
|
||||
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
|
||||
@Import(MessageMatcherAuthorizationManagerConfiguration.class)
|
||||
final class WebSocketMessageBrokerSecurityConfiguration
|
||||
implements WebSocketMessageBrokerConfigurer, SmartInitializingSingleton {
|
||||
|
||||
private static final String SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME = "stompWebSocketHandlerMapping";
|
||||
|
||||
private MessageMatcherDelegatingAuthorizationManager b;
|
||||
|
||||
private static final AuthorizationManager<Message<?>> ANY_MESSAGE_AUTHENTICATED = MessageMatcherDelegatingAuthorizationManager
|
||||
.builder().anyMessage().authenticated().build();
|
||||
|
||||
private final ChannelInterceptor securityContextChannelInterceptor = new SecurityContextChannelInterceptor();
|
||||
|
||||
private final ChannelInterceptor csrfChannelInterceptor = new CsrfChannelInterceptor();
|
||||
|
||||
private AuthorizationChannelInterceptor authorizationChannelInterceptor = new AuthorizationChannelInterceptor(
|
||||
ANY_MESSAGE_AUTHENTICATED);
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
WebSocketMessageBrokerSecurityConfiguration(ApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureClientInboundChannel(ChannelRegistration registration) {
|
||||
this.authorizationChannelInterceptor
|
||||
.setAuthorizationEventPublisher(new SpringAuthorizationEventPublisher(this.context));
|
||||
registration.interceptors(this.securityContextChannelInterceptor, this.csrfChannelInterceptor,
|
||||
this.authorizationChannelInterceptor);
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAuthorizationManager(AuthorizationManager<Message<?>> authorizationManager) {
|
||||
this.authorizationChannelInterceptor = new AuthorizationChannelInterceptor(authorizationManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
SimpleUrlHandlerMapping mapping = getBeanOrNull(SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME,
|
||||
SimpleUrlHandlerMapping.class);
|
||||
if (mapping == null) {
|
||||
return;
|
||||
}
|
||||
configureCsrf(mapping);
|
||||
}
|
||||
|
||||
private <T> T getBeanOrNull(String name, Class<T> type) {
|
||||
Map<String, T> beans = this.context.getBeansOfType(type);
|
||||
return beans.get(name);
|
||||
}
|
||||
|
||||
private void configureCsrf(SimpleUrlHandlerMapping mapping) {
|
||||
Map<String, Object> mappings = mapping.getHandlerMap();
|
||||
for (Object object : mappings.values()) {
|
||||
if (object instanceof SockJsHttpRequestHandler) {
|
||||
setHandshakeInterceptors((SockJsHttpRequestHandler) object);
|
||||
}
|
||||
else if (object instanceof WebSocketHttpRequestHandler) {
|
||||
setHandshakeInterceptors((WebSocketHttpRequestHandler) object);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"Bean " + SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME + " is expected to contain mappings to either a "
|
||||
+ "SockJsHttpRequestHandler or a WebSocketHttpRequestHandler but got " + object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setHandshakeInterceptors(SockJsHttpRequestHandler handler) {
|
||||
SockJsService sockJsService = handler.getSockJsService();
|
||||
Assert.state(sockJsService instanceof TransportHandlingSockJsService,
|
||||
() -> "sockJsService must be instance of TransportHandlingSockJsService got " + sockJsService);
|
||||
TransportHandlingSockJsService transportHandlingSockJsService = (TransportHandlingSockJsService) sockJsService;
|
||||
List<HandshakeInterceptor> handshakeInterceptors = transportHandlingSockJsService.getHandshakeInterceptors();
|
||||
List<HandshakeInterceptor> interceptorsToSet = new ArrayList<>(handshakeInterceptors.size() + 1);
|
||||
interceptorsToSet.add(new CsrfTokenHandshakeInterceptor());
|
||||
interceptorsToSet.addAll(handshakeInterceptors);
|
||||
transportHandlingSockJsService.setHandshakeInterceptors(interceptorsToSet);
|
||||
}
|
||||
|
||||
private void setHandshakeInterceptors(WebSocketHttpRequestHandler handler) {
|
||||
List<HandshakeInterceptor> handshakeInterceptors = handler.getHandshakeInterceptors();
|
||||
List<HandshakeInterceptor> interceptorsToSet = new ArrayList<>(handshakeInterceptors.size() + 1);
|
||||
interceptorsToSet.add(new CsrfTokenHandshakeInterceptor());
|
||||
interceptorsToSet.addAll(handshakeInterceptors);
|
||||
handler.setHandshakeInterceptors(interceptorsToSet);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.springframework.security.config.websocket;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
@@ -37,20 +38,34 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.beans.factory.xml.XmlReaderContext;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.simp.SimpMessageType;
|
||||
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
|
||||
import org.springframework.security.access.expression.ExpressionUtils;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.access.vote.ConsensusBased;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.config.Elements;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory;
|
||||
import org.springframework.security.messaging.access.expression.MessageAuthorizationContextSecurityExpressionHandler;
|
||||
import org.springframework.security.messaging.access.expression.MessageExpressionVoter;
|
||||
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
|
||||
import org.springframework.security.messaging.access.intercept.ChannelSecurityInterceptor;
|
||||
import org.springframework.security.messaging.access.intercept.MessageAuthorizationContext;
|
||||
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
|
||||
import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
|
||||
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
|
||||
import org.springframework.security.messaging.util.matcher.MessageMatcher;
|
||||
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
|
||||
import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher;
|
||||
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
|
||||
import org.springframework.security.messaging.web.socket.server.CsrfTokenHandshakeInterceptor;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
@@ -99,6 +114,10 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
|
||||
|
||||
private static final String DISABLED_ATTR = "same-origin-disabled";
|
||||
|
||||
private static final String USE_AUTHORIZATION_MANAGER_ATTR = "use-authorization-manager";
|
||||
|
||||
private static final String AUTHORIZATION_MANAGER_REF_ATTR = "authorization-manager-ref";
|
||||
|
||||
private static final String PATTERN_ATTR = "pattern";
|
||||
|
||||
private static final String ACCESS_ATTR = "access";
|
||||
@@ -114,14 +133,83 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
|
||||
*/
|
||||
@Override
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
String id = element.getAttribute(ID_ATTR);
|
||||
String inSecurityInterceptorName = parseAuthorization(element, parserContext);
|
||||
BeanDefinitionRegistry registry = parserContext.getRegistry();
|
||||
if (StringUtils.hasText(id)) {
|
||||
registry.registerAlias(inSecurityInterceptorName, id);
|
||||
if (!registry.containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
|
||||
registry.registerBeanDefinition(PATH_MATCHER_BEAN_NAME, new RootBeanDefinition(AntPathMatcher.class));
|
||||
}
|
||||
}
|
||||
else {
|
||||
boolean sameOriginDisabled = Boolean.parseBoolean(element.getAttribute(DISABLED_ATTR));
|
||||
XmlReaderContext context = parserContext.getReaderContext();
|
||||
BeanDefinitionBuilder mspp = BeanDefinitionBuilder.rootBeanDefinition(MessageSecurityPostProcessor.class);
|
||||
mspp.addConstructorArgValue(inSecurityInterceptorName);
|
||||
mspp.addConstructorArgValue(sameOriginDisabled);
|
||||
context.registerWithGeneratedName(mspp.getBeanDefinition());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String parseAuthorization(Element element, ParserContext parserContext) {
|
||||
boolean useAuthorizationManager = Boolean.parseBoolean(element.getAttribute(USE_AUTHORIZATION_MANAGER_ATTR));
|
||||
if (useAuthorizationManager) {
|
||||
return parseAuthorizationManager(element, parserContext);
|
||||
}
|
||||
if (StringUtils.hasText(element.getAttribute(AUTHORIZATION_MANAGER_REF_ATTR))) {
|
||||
return parseAuthorizationManager(element, parserContext);
|
||||
}
|
||||
return parseSecurityMetadataSource(element, parserContext);
|
||||
}
|
||||
|
||||
private String parseAuthorizationManager(Element element, ParserContext parserContext) {
|
||||
XmlReaderContext context = parserContext.getReaderContext();
|
||||
String mdsId = createAuthorizationManager(element, parserContext);
|
||||
BeanDefinitionBuilder inboundChannelSecurityInterceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(AuthorizationChannelInterceptor.class);
|
||||
inboundChannelSecurityInterceptor.addConstructorArgReference(mdsId);
|
||||
return context.registerWithGeneratedName(inboundChannelSecurityInterceptor.getBeanDefinition());
|
||||
}
|
||||
|
||||
private String createAuthorizationManager(Element element, ParserContext parserContext) {
|
||||
XmlReaderContext context = parserContext.getReaderContext();
|
||||
String authorizationManagerRef = element.getAttribute(AUTHORIZATION_MANAGER_REF_ATTR);
|
||||
if (StringUtils.hasText(authorizationManagerRef)) {
|
||||
return authorizationManagerRef;
|
||||
}
|
||||
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
|
||||
String expressionHandlerRef = (expressionHandlerElt != null) ? expressionHandlerElt.getAttribute("ref") : null;
|
||||
ManagedMap<BeanDefinition, BeanDefinition> matcherToExpression = new ManagedMap<>();
|
||||
List<Element> interceptMessages = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_MESSAGE);
|
||||
for (Element interceptMessage : interceptMessages) {
|
||||
String matcherPattern = interceptMessage.getAttribute(PATTERN_ATTR);
|
||||
String accessExpression = interceptMessage.getAttribute(ACCESS_ATTR);
|
||||
String messageType = interceptMessage.getAttribute(TYPE_ATTR);
|
||||
BeanDefinition matcher = createMatcher(matcherPattern, messageType, parserContext, interceptMessage);
|
||||
BeanDefinitionBuilder authorizationManager = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(ExpressionBasedAuthorizationManager.class);
|
||||
if (StringUtils.hasText(expressionHandlerRef)) {
|
||||
authorizationManager.addConstructorArgReference(expressionHandlerRef);
|
||||
}
|
||||
authorizationManager.addConstructorArgValue(accessExpression);
|
||||
matcherToExpression.put(matcher, authorizationManager.getBeanDefinition());
|
||||
}
|
||||
BeanDefinitionBuilder mds = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(MessageMatcherDelegatingAuthorizationManagerFactory.class);
|
||||
mds.setFactoryMethod("createMessageMatcherDelegatingAuthorizationManager");
|
||||
mds.addConstructorArgValue(matcherToExpression);
|
||||
return context.registerWithGeneratedName(mds.getBeanDefinition());
|
||||
}
|
||||
|
||||
private String parseSecurityMetadataSource(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionRegistry registry = parserContext.getRegistry();
|
||||
XmlReaderContext context = parserContext.getReaderContext();
|
||||
ManagedMap<BeanDefinition, String> matcherToExpression = new ManagedMap<>();
|
||||
String id = element.getAttribute(ID_ATTR);
|
||||
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
|
||||
String expressionHandlerRef = (expressionHandlerElt != null) ? expressionHandlerElt.getAttribute("ref") : null;
|
||||
boolean expressionHandlerDefined = StringUtils.hasText(expressionHandlerRef);
|
||||
boolean sameOriginDisabled = Boolean.parseBoolean(element.getAttribute(DISABLED_ATTR));
|
||||
List<Element> interceptMessages = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_MESSAGE);
|
||||
for (Element interceptMessage : interceptMessages) {
|
||||
String matcherPattern = interceptMessage.getAttribute(PATTERN_ATTR);
|
||||
@@ -151,21 +239,7 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
|
||||
.rootBeanDefinition(ChannelSecurityInterceptor.class);
|
||||
inboundChannelSecurityInterceptor.addConstructorArgValue(registry.getBeanDefinition(mdsId));
|
||||
inboundChannelSecurityInterceptor.addPropertyValue("accessDecisionManager", adm.getBeanDefinition());
|
||||
String inSecurityInterceptorName = context
|
||||
.registerWithGeneratedName(inboundChannelSecurityInterceptor.getBeanDefinition());
|
||||
if (StringUtils.hasText(id)) {
|
||||
registry.registerAlias(inSecurityInterceptorName, id);
|
||||
if (!registry.containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
|
||||
registry.registerBeanDefinition(PATH_MATCHER_BEAN_NAME, new RootBeanDefinition(AntPathMatcher.class));
|
||||
}
|
||||
}
|
||||
else {
|
||||
BeanDefinitionBuilder mspp = BeanDefinitionBuilder.rootBeanDefinition(MessageSecurityPostProcessor.class);
|
||||
mspp.addConstructorArgValue(inSecurityInterceptorName);
|
||||
mspp.addConstructorArgValue(sameOriginDisabled);
|
||||
context.registerWithGeneratedName(mspp.getBeanDefinition());
|
||||
}
|
||||
return null;
|
||||
return context.registerWithGeneratedName(inboundChannelSecurityInterceptor.getBeanDefinition());
|
||||
}
|
||||
|
||||
private BeanDefinition createMatcher(String matcherPattern, String messageType, ParserContext parserContext,
|
||||
@@ -341,4 +415,48 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
|
||||
|
||||
}
|
||||
|
||||
private static final class ExpressionBasedAuthorizationManager
|
||||
implements AuthorizationManager<MessageAuthorizationContext<?>> {
|
||||
|
||||
private final SecurityExpressionHandler<MessageAuthorizationContext<?>> expressionHandler;
|
||||
|
||||
private final Expression expression;
|
||||
|
||||
private ExpressionBasedAuthorizationManager(String expression) {
|
||||
this(new MessageAuthorizationContextSecurityExpressionHandler(), expression);
|
||||
}
|
||||
|
||||
private ExpressionBasedAuthorizationManager(
|
||||
SecurityExpressionHandler<MessageAuthorizationContext<?>> expressionHandler, String expression) {
|
||||
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
|
||||
Assert.notNull(expression, "expression cannot be null");
|
||||
this.expressionHandler = expressionHandler;
|
||||
this.expression = this.expressionHandler.getExpressionParser().parseExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication,
|
||||
MessageAuthorizationContext<?> object) {
|
||||
EvaluationContext context = this.expressionHandler.createEvaluationContext(authentication, object);
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, context);
|
||||
return new AuthorizationDecision(granted);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MessageMatcherDelegatingAuthorizationManagerFactory {
|
||||
|
||||
private static AuthorizationManager<Message<?>> createMessageMatcherDelegatingAuthorizationManager(
|
||||
Map<MessageMatcher<?>, AuthorizationManager<MessageAuthorizationContext<?>>> beans) {
|
||||
MessageMatcherDelegatingAuthorizationManager.Builder builder = MessageMatcherDelegatingAuthorizationManager
|
||||
.builder();
|
||||
for (Map.Entry<MessageMatcher<?>, AuthorizationManager<MessageAuthorizationContext<?>>> entry : beans
|
||||
.entrySet()) {
|
||||
builder.matchers(entry.getKey()).access(entry.getValue());
|
||||
}
|
||||
return builder.anyMessage().permitAll().build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -291,6 +291,12 @@ websocket-message-broker.attrlist &=
|
||||
websocket-message-broker.attrlist &=
|
||||
## Disables the requirement for CSRF token to be present in the Stomp headers (default false). Changing the default is useful if it is necessary to allow other origins to make SockJS connections.
|
||||
attribute same-origin-disabled {xsd:boolean}?
|
||||
websocket-message-broker.attlist &=
|
||||
## Use this AuthorizationManager instead of deriving one from <intercept-message> elements
|
||||
attribute authorization-manager-ref {xsd:string}?
|
||||
websocket-message-broker.attrlist &=
|
||||
## Use AuthorizationManager API instead of SecurityMetadatasource
|
||||
attribute use-authorization-manager {xsd:boolean}?
|
||||
|
||||
intercept-message =
|
||||
## Creates an authorization rule for a websocket message.
|
||||
|
||||
@@ -915,6 +915,20 @@
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="use-authorization-manager" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Use AuthorizationManager API instead of SecurityMetadatasource
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:attributeGroup name="websocket-message-broker.attlist">
|
||||
<xs:attribute name="authorization-manager-ref" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Use this AuthorizationManager instead of deriving one from <intercept-message> elements
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="intercept-message">
|
||||
<xs:annotation>
|
||||
|
||||
@@ -291,6 +291,12 @@ websocket-message-broker.attrlist &=
|
||||
websocket-message-broker.attrlist &=
|
||||
## Disables the requirement for CSRF token to be present in the Stomp headers (default false). Changing the default is useful if it is necessary to allow other origins to make SockJS connections.
|
||||
attribute same-origin-disabled {xsd:boolean}?
|
||||
websocket-message-broker.attlist &=
|
||||
## Use this AuthorizationManager instead of deriving one from <intercept-message> elements
|
||||
attribute authorization-manager-ref {xsd:string}?
|
||||
websocket-message-broker.attrlist &=
|
||||
## Use AuthorizationManager API instead of SecurityMetadatasource
|
||||
attribute use-authorization-manager {xsd:boolean}?
|
||||
|
||||
intercept-message =
|
||||
## Creates an authorization rule for a websocket message.
|
||||
|
||||
@@ -915,6 +915,20 @@
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="use-authorization-manager" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Use AuthorizationManager API instead of SecurityMetadatasource
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:attributeGroup name="websocket-message-broker.attlist">
|
||||
<xs:attribute name="authorization-manager-ref" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Use this AuthorizationManager instead of deriving one from <intercept-message> elements
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="intercept-message">
|
||||
<xs:annotation>
|
||||
|
||||
Reference in New Issue
Block a user