6 changed files with 0 additions and 1314 deletions
@ -1,286 +0,0 @@
@@ -1,286 +0,0 @@
|
||||
/* |
||||
* 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.NoSuchBeanDefinitionException; |
||||
import org.springframework.beans.factory.SmartInitializingSingleton; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.annotation.Bean; |
||||
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.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.ObjectPostProcessor; |
||||
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration; |
||||
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry; |
||||
import org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler; |
||||
import org.springframework.security.messaging.access.expression.MessageExpressionVoter; |
||||
import org.springframework.security.messaging.access.intercept.ChannelSecurityInterceptor; |
||||
import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource; |
||||
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.AntPathMatcher; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.PathMatcher; |
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; |
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry; |
||||
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; |
||||
|
||||
/** |
||||
* Allows configuring WebSocket Authorization. |
||||
* |
||||
* <p> |
||||
* For example: |
||||
* </p> |
||||
* |
||||
* <pre> |
||||
* @Configuration |
||||
* public class WebSocketSecurityConfig extends |
||||
* AbstractSecurityWebSocketMessageBrokerConfigurer { |
||||
* |
||||
* @Override |
||||
* protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { |
||||
* messages.simpDestMatchers("/user/queue/errors").permitAll() |
||||
* .simpDestMatchers("/admin/**").hasRole("ADMIN").anyMessage() |
||||
* .authenticated(); |
||||
* } |
||||
* } |
||||
* </pre> |
||||
* |
||||
* @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 { |
||||
|
||||
private final WebSocketMessageSecurityMetadataSourceRegistry inboundRegistry = new WebSocketMessageSecurityMetadataSourceRegistry(); |
||||
|
||||
private SecurityExpressionHandler<Message<Object>> defaultExpressionHandler = new DefaultMessageSecurityExpressionHandler<>(); |
||||
|
||||
private SecurityExpressionHandler<Message<Object>> expressionHandler; |
||||
|
||||
private ApplicationContext context; |
||||
|
||||
@Override |
||||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
} |
||||
|
||||
@Override |
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { |
||||
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver()); |
||||
} |
||||
|
||||
@Override |
||||
public final void configureClientInboundChannel(ChannelRegistration registration) { |
||||
ChannelSecurityInterceptor inboundChannelSecurity = this.context.getBean(ChannelSecurityInterceptor.class); |
||||
registration.interceptors(this.context.getBean(SecurityContextChannelInterceptor.class)); |
||||
if (!sameOriginDisabled()) { |
||||
registration.interceptors(this.context.getBean(CsrfChannelInterceptor.class)); |
||||
} |
||||
if (this.inboundRegistry.containsMapping()) { |
||||
registration.interceptors(inboundChannelSecurity); |
||||
} |
||||
customizeClientInboundChannel(registration); |
||||
} |
||||
|
||||
private PathMatcher getDefaultPathMatcher() { |
||||
try { |
||||
return this.context.getBean(SimpAnnotationMethodMessageHandler.class).getPathMatcher(); |
||||
} |
||||
catch (NoSuchBeanDefinitionException ex) { |
||||
return new AntPathMatcher(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* <p> |
||||
* Determines if a CSRF token is required for connecting. This protects against remote |
||||
* sites from connecting to the application and being able to read/write data over the |
||||
* connection. The default is false (the token is required). |
||||
* </p> |
||||
* <p> |
||||
* Subclasses can override this method to disable CSRF protection |
||||
* </p> |
||||
* @return false if a CSRF token is required for connecting, else true |
||||
*/ |
||||
protected boolean sameOriginDisabled() { |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Allows subclasses to customize the configuration of the {@link ChannelRegistration} |
||||
* . |
||||
* @param registration the {@link ChannelRegistration} to customize |
||||
*/ |
||||
protected void customizeClientInboundChannel(ChannelRegistration registration) { |
||||
} |
||||
|
||||
@Bean |
||||
public CsrfChannelInterceptor csrfChannelInterceptor() { |
||||
return new CsrfChannelInterceptor(); |
||||
} |
||||
|
||||
@Bean |
||||
public ChannelSecurityInterceptor inboundChannelSecurity( |
||||
MessageSecurityMetadataSource messageSecurityMetadataSource) { |
||||
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor( |
||||
messageSecurityMetadataSource); |
||||
MessageExpressionVoter<Object> voter = new MessageExpressionVoter<>(); |
||||
voter.setExpressionHandler(getMessageExpressionHandler()); |
||||
List<AccessDecisionVoter<?>> voters = new ArrayList<>(); |
||||
voters.add(voter); |
||||
AffirmativeBased manager = new AffirmativeBased(voters); |
||||
channelSecurityInterceptor.setAccessDecisionManager(manager); |
||||
return channelSecurityInterceptor; |
||||
} |
||||
|
||||
@Bean |
||||
public SecurityContextChannelInterceptor securityContextChannelInterceptor() { |
||||
return new SecurityContextChannelInterceptor(); |
||||
} |
||||
|
||||
@Bean |
||||
public MessageSecurityMetadataSource inboundMessageSecurityMetadataSource() { |
||||
this.inboundRegistry.expressionHandler(getMessageExpressionHandler()); |
||||
configureInbound(this.inboundRegistry); |
||||
return this.inboundRegistry.createMetadataSource(); |
||||
} |
||||
|
||||
/** |
||||
* @param messages |
||||
*/ |
||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { |
||||
} |
||||
|
||||
@Autowired |
||||
public void setApplicationContext(ApplicationContext context) { |
||||
this.context = context; |
||||
} |
||||
|
||||
@Deprecated |
||||
public void setMessageExpessionHandler(List<SecurityExpressionHandler<Message<Object>>> expressionHandlers) { |
||||
setMessageExpressionHandler(expressionHandlers); |
||||
} |
||||
|
||||
@Autowired(required = false) |
||||
public void setMessageExpressionHandler(List<SecurityExpressionHandler<Message<Object>>> expressionHandlers) { |
||||
if (expressionHandlers.size() == 1) { |
||||
this.expressionHandler = expressionHandlers.get(0); |
||||
} |
||||
} |
||||
|
||||
@Autowired(required = false) |
||||
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) { |
||||
this.defaultExpressionHandler = objectPostProcessor.postProcess(this.defaultExpressionHandler); |
||||
} |
||||
|
||||
private SecurityExpressionHandler<Message<Object>> getMessageExpressionHandler() { |
||||
if (this.expressionHandler == null) { |
||||
return this.defaultExpressionHandler; |
||||
} |
||||
return this.expressionHandler; |
||||
} |
||||
|
||||
@Override |
||||
public void afterSingletonsInstantiated() { |
||||
if (sameOriginDisabled()) { |
||||
return; |
||||
} |
||||
String beanName = "stompWebSocketHandlerMapping"; |
||||
SimpleUrlHandlerMapping mapping = this.context.getBean(beanName, SimpleUrlHandlerMapping.class); |
||||
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 " + beanName + " is expected to contain mappings to either a " |
||||
+ "SockJsHttpRequestHandler or a WebSocketHttpRequestHandler but got " + object); |
||||
} |
||||
} |
||||
if (this.inboundRegistry.containsMapping() && !this.inboundRegistry.isSimpDestPathMatcherConfigured()) { |
||||
PathMatcher pathMatcher = getDefaultPathMatcher(); |
||||
this.inboundRegistry.simpDestPathMatcher(pathMatcher); |
||||
} |
||||
} |
||||
|
||||
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); |
||||
} |
||||
|
||||
private static class WebSocketMessageSecurityMetadataSourceRegistry extends MessageSecurityMetadataSourceRegistry { |
||||
|
||||
@Override |
||||
public MessageSecurityMetadataSource createMetadataSource() { |
||||
return super.createMetadataSource(); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean containsMapping() { |
||||
return super.containsMapping(); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean isSimpDestPathMatcherConfigured() { |
||||
return super.isSimpDestPathMatcherConfigured(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,178 +0,0 @@
@@ -1,178 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2016 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.HashMap; |
||||
|
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.MessageChannel; |
||||
import org.springframework.messaging.MessageDeliveryException; |
||||
import org.springframework.messaging.handler.annotation.MessageMapping; |
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor; |
||||
import org.springframework.messaging.simp.SimpMessageType; |
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry; |
||||
import org.springframework.messaging.support.GenericMessage; |
||||
import org.springframework.mock.web.MockServletConfig; |
||||
import org.springframework.security.access.AccessDeniedException; |
||||
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry; |
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal; |
||||
import org.springframework.security.web.csrf.CsrfToken; |
||||
import org.springframework.security.web.csrf.DefaultCsrfToken; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; |
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; |
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry; |
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
public class AbstractSecurityWebSocketMessageBrokerConfigurerDocTests { |
||||
|
||||
AnnotationConfigWebApplicationContext context; |
||||
|
||||
TestingAuthenticationToken messageUser; |
||||
|
||||
CsrfToken token; |
||||
|
||||
String sessionAttr; |
||||
|
||||
@BeforeEach |
||||
public void setup() { |
||||
this.token = new DefaultCsrfToken("header", "param", "token"); |
||||
this.sessionAttr = "sessionAttr"; |
||||
this.messageUser = new TestingAuthenticationToken("user", "pass", "ROLE_USER"); |
||||
} |
||||
|
||||
@AfterEach |
||||
public void cleanup() { |
||||
if (this.context != null) { |
||||
this.context.close(); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void securityMappings() { |
||||
loadConfig(WebSocketSecurityConfig.class); |
||||
clientInboundChannel().send(message("/user/queue/errors", SimpMessageType.SUBSCRIBE)); |
||||
assertThatExceptionOfType(MessageDeliveryException.class) |
||||
.isThrownBy(() -> clientInboundChannel().send(message("/denyAll", SimpMessageType.MESSAGE))) |
||||
.withCauseInstanceOf(AccessDeniedException.class); |
||||
} |
||||
|
||||
private void loadConfig(Class<?>... configs) { |
||||
this.context = new AnnotationConfigWebApplicationContext(); |
||||
this.context.register(configs); |
||||
this.context.register(WebSocketConfig.class, SyncExecutorConfig.class); |
||||
this.context.setServletConfig(new MockServletConfig()); |
||||
this.context.refresh(); |
||||
} |
||||
|
||||
private MessageChannel clientInboundChannel() { |
||||
return this.context.getBean("clientInboundChannel", MessageChannel.class); |
||||
} |
||||
|
||||
private Message<String> message(String destination, SimpMessageType type) { |
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(type); |
||||
return message(headers, destination); |
||||
} |
||||
|
||||
private Message<String> message(SimpMessageHeaderAccessor headers, String destination) { |
||||
headers.setSessionId("123"); |
||||
headers.setSessionAttributes(new HashMap<>()); |
||||
if (destination != null) { |
||||
headers.setDestination(destination); |
||||
} |
||||
if (this.messageUser != null) { |
||||
headers.setUser(this.messageUser); |
||||
} |
||||
return new GenericMessage<>("hi", headers.getMessageHeaders()); |
||||
} |
||||
|
||||
@Controller |
||||
static class MyController { |
||||
|
||||
@MessageMapping("/authentication") |
||||
void authentication(@AuthenticationPrincipal String un) { |
||||
// ... do something ...
|
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
static class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { |
||||
|
||||
@Override |
||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { |
||||
messages.nullDestMatcher() |
||||
.authenticated() |
||||
// <1>
|
||||
.simpSubscribeDestMatchers("/user/queue/errors") |
||||
.permitAll() |
||||
// <2>
|
||||
.simpDestMatchers("/app/**") |
||||
.hasRole("USER") |
||||
// <3>
|
||||
.simpSubscribeDestMatchers("/user/**", "/topic/friends/*") |
||||
.hasRole("USER") // <4>
|
||||
.simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE) |
||||
.denyAll() // <5>
|
||||
.anyMessage() |
||||
.denyAll(); // <6>
|
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebSocketMessageBroker |
||||
static class WebSocketConfig implements WebSocketMessageBrokerConfigurer { |
||||
|
||||
@Override |
||||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
registry.addEndpoint("/chat").withSockJS(); |
||||
} |
||||
|
||||
@Override |
||||
public void configureMessageBroker(MessageBrokerRegistry registry) { |
||||
registry.enableSimpleBroker("/queue/", "/topic/"); |
||||
registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll"); |
||||
} |
||||
|
||||
@Bean |
||||
MyController myController() { |
||||
return new MyController(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
static class SyncExecutorConfig { |
||||
|
||||
@Bean |
||||
static SyncExecutorSubscribableChannelPostProcessor postProcessor() { |
||||
return new SyncExecutorSubscribableChannelPostProcessor(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,733 +0,0 @@
@@ -1,733 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2023 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.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Import; |
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.http.server.ServerHttpRequest; |
||||
import org.springframework.http.server.ServerHttpResponse; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.MessageChannel; |
||||
import org.springframework.messaging.MessageDeliveryException; |
||||
import org.springframework.messaging.handler.annotation.MessageMapping; |
||||
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; |
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor; |
||||
import org.springframework.messaging.simp.SimpMessageType; |
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry; |
||||
import org.springframework.messaging.support.AbstractMessageChannel; |
||||
import org.springframework.messaging.support.GenericMessage; |
||||
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.messaging.access.intercept.ChannelSecurityInterceptor; |
||||
import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource; |
||||
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor; |
||||
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor; |
||||
import org.springframework.security.web.csrf.CsrfToken; |
||||
import org.springframework.security.web.csrf.DefaultCsrfToken; |
||||
import org.springframework.security.web.csrf.DeferredCsrfToken; |
||||
import org.springframework.security.web.csrf.MissingCsrfTokenException; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.test.util.ReflectionTestUtils; |
||||
import org.springframework.util.AntPathMatcher; |
||||
import org.springframework.web.HttpRequestHandler; |
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; |
||||
import org.springframework.web.servlet.HandlerMapping; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; |
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry; |
||||
import org.springframework.web.socket.server.HandshakeFailureException; |
||||
import org.springframework.web.socket.server.HandshakeHandler; |
||||
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; |
||||
import org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler; |
||||
import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.springframework.security.web.csrf.CsrfTokenAssert.assertThatCsrfToken; |
||||
|
||||
public class AbstractSecurityWebSocketMessageBrokerConfigurerTests { |
||||
|
||||
AnnotationConfigWebApplicationContext context; |
||||
|
||||
TestingAuthenticationToken messageUser; |
||||
|
||||
CsrfToken token; |
||||
|
||||
String sessionAttr; |
||||
|
||||
@BeforeEach |
||||
public void setup() { |
||||
this.token = new DefaultCsrfToken("header", "param", "token"); |
||||
this.sessionAttr = "sessionAttr"; |
||||
this.messageUser = new TestingAuthenticationToken("user", "pass", "ROLE_USER"); |
||||
} |
||||
|
||||
@AfterEach |
||||
public void cleanup() { |
||||
if (this.context != null) { |
||||
this.context.close(); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void simpleRegistryMappings() { |
||||
loadConfig(SockJsSecurityConfig.class); |
||||
clientInboundChannel().send(message("/permitAll")); |
||||
assertThatExceptionOfType(MessageDeliveryException.class) |
||||
.isThrownBy(() -> clientInboundChannel().send(message("/denyAll"))) |
||||
.withCauseInstanceOf(AccessDeniedException.class); |
||||
} |
||||
|
||||
@Test |
||||
public void annonymousSupported() { |
||||
loadConfig(SockJsSecurityConfig.class); |
||||
this.messageUser = null; |
||||
clientInboundChannel().send(message("/permitAll")); |
||||
} |
||||
|
||||
// gh-3797
|
||||
@Test |
||||
public void beanResolver() { |
||||
loadConfig(SockJsSecurityConfig.class); |
||||
this.messageUser = null; |
||||
clientInboundChannel().send(message("/beanResolver")); |
||||
} |
||||
|
||||
@Test |
||||
public void addsAuthenticationPrincipalResolver() { |
||||
loadConfig(SockJsSecurityConfig.class); |
||||
MessageChannel messageChannel = clientInboundChannel(); |
||||
Message<String> message = message("/permitAll/authentication"); |
||||
messageChannel.send(message); |
||||
assertThat(this.context.getBean(MyController.class).authenticationPrincipal) |
||||
.isEqualTo((String) this.messageUser.getPrincipal()); |
||||
} |
||||
|
||||
@Test |
||||
public void addsAuthenticationPrincipalResolverWhenNoAuthorization() { |
||||
loadConfig(NoInboundSecurityConfig.class); |
||||
MessageChannel messageChannel = clientInboundChannel(); |
||||
Message<String> message = message("/permitAll/authentication"); |
||||
messageChannel.send(message); |
||||
assertThat(this.context.getBean(MyController.class).authenticationPrincipal) |
||||
.isEqualTo((String) this.messageUser.getPrincipal()); |
||||
} |
||||
|
||||
@Test |
||||
public void addsCsrfProtectionWhenNoAuthorization() { |
||||
loadConfig(NoInboundSecurityConfig.class); |
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); |
||||
Message<?> message = message(headers, "/authentication"); |
||||
MessageChannel messageChannel = clientInboundChannel(); |
||||
assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() -> messageChannel.send(message)) |
||||
.withCauseInstanceOf(MissingCsrfTokenException.class); |
||||
} |
||||
|
||||
@Test |
||||
public void csrfProtectionForConnect() { |
||||
loadConfig(SockJsSecurityConfig.class); |
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); |
||||
Message<?> message = message(headers, "/authentication"); |
||||
MessageChannel messageChannel = clientInboundChannel(); |
||||
assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() -> messageChannel.send(message)) |
||||
.withCauseInstanceOf(MissingCsrfTokenException.class); |
||||
} |
||||
|
||||
@Test |
||||
public void csrfProtectionDisabledForConnect() { |
||||
loadConfig(CsrfDisabledSockJsSecurityConfig.class); |
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); |
||||
Message<?> message = message(headers, "/permitAll/connect"); |
||||
MessageChannel messageChannel = clientInboundChannel(); |
||||
messageChannel.send(message); |
||||
} |
||||
|
||||
@Test |
||||
public void csrfProtectionDefinedByBean() { |
||||
loadConfig(SockJsProxylessSecurityConfig.class); |
||||
MessageChannel messageChannel = clientInboundChannel(); |
||||
CsrfChannelInterceptor csrfChannelInterceptor = this.context.getBean(CsrfChannelInterceptor.class); |
||||
assertThat(((AbstractMessageChannel) messageChannel).getInterceptors()).contains(csrfChannelInterceptor); |
||||
} |
||||
|
||||
@Test |
||||
public void messagesConnectUseCsrfTokenHandshakeInterceptor() throws Exception { |
||||
loadConfig(SockJsSecurityConfig.class); |
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); |
||||
Message<?> message = message(headers, "/authentication"); |
||||
MockHttpServletRequest request = sockjsHttpRequest("/chat"); |
||||
HttpRequestHandler handler = handler(request); |
||||
handler.handleRequest(request, new MockHttpServletResponse()); |
||||
assertHandshake(request); |
||||
} |
||||
|
||||
@Test |
||||
public void messagesConnectUseCsrfTokenHandshakeInterceptorMultipleMappings() throws Exception { |
||||
loadConfig(SockJsSecurityConfig.class); |
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); |
||||
Message<?> message = message(headers, "/authentication"); |
||||
MockHttpServletRequest request = sockjsHttpRequest("/other"); |
||||
HttpRequestHandler handler = handler(request); |
||||
handler.handleRequest(request, new MockHttpServletResponse()); |
||||
assertHandshake(request); |
||||
} |
||||
|
||||
@Test |
||||
public void messagesConnectWebSocketUseCsrfTokenHandshakeInterceptor() throws Exception { |
||||
loadConfig(WebSocketSecurityConfig.class); |
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); |
||||
Message<?> message = message(headers, "/authentication"); |
||||
MockHttpServletRequest request = websocketHttpRequest("/websocket"); |
||||
HttpRequestHandler handler = handler(request); |
||||
handler.handleRequest(request, new MockHttpServletResponse()); |
||||
assertHandshake(request); |
||||
} |
||||
|
||||
@Test |
||||
public void msmsRegistryCustomPatternMatcher() { |
||||
loadConfig(MsmsRegistryCustomPatternMatcherConfig.class); |
||||
clientInboundChannel().send(message("/app/a.b")); |
||||
assertThatExceptionOfType(MessageDeliveryException.class) |
||||
.isThrownBy(() -> clientInboundChannel().send(message("/app/a.b.c"))) |
||||
.withCauseInstanceOf(AccessDeniedException.class); |
||||
} |
||||
|
||||
@Test |
||||
public void overrideMsmsRegistryCustomPatternMatcher() { |
||||
loadConfig(OverrideMsmsRegistryCustomPatternMatcherConfig.class); |
||||
clientInboundChannel().send(message("/app/a/b")); |
||||
assertThatExceptionOfType(MessageDeliveryException.class) |
||||
.isThrownBy(() -> clientInboundChannel().send(message("/app/a/b/c"))) |
||||
.withCauseInstanceOf(AccessDeniedException.class); |
||||
} |
||||
|
||||
@Test |
||||
public void defaultPatternMatcher() { |
||||
loadConfig(DefaultPatternMatcherConfig.class); |
||||
clientInboundChannel().send(message("/app/a/b")); |
||||
assertThatExceptionOfType(MessageDeliveryException.class) |
||||
.isThrownBy(() -> clientInboundChannel().send(message("/app/a/b/c"))) |
||||
.withCauseInstanceOf(AccessDeniedException.class); |
||||
} |
||||
|
||||
@Test |
||||
public void customExpression() { |
||||
loadConfig(CustomExpressionConfig.class); |
||||
clientInboundChannel().send(message("/denyRob")); |
||||
this.messageUser = new TestingAuthenticationToken("rob", "password", "ROLE_USER"); |
||||
assertThatExceptionOfType(MessageDeliveryException.class) |
||||
.isThrownBy(() -> clientInboundChannel().send(message("/denyRob"))) |
||||
.withCauseInstanceOf(AccessDeniedException.class); |
||||
} |
||||
|
||||
@Test |
||||
public void channelSecurityInterceptorUsesMetadataSourceBeanWhenProxyingDisabled() { |
||||
loadConfig(SockJsProxylessSecurityConfig.class); |
||||
ChannelSecurityInterceptor channelSecurityInterceptor = this.context.getBean(ChannelSecurityInterceptor.class); |
||||
MessageSecurityMetadataSource messageSecurityMetadataSource = this.context |
||||
.getBean(MessageSecurityMetadataSource.class); |
||||
assertThat(channelSecurityInterceptor.obtainSecurityMetadataSource()).isSameAs(messageSecurityMetadataSource); |
||||
} |
||||
|
||||
@Test |
||||
public void securityContextChannelInterceptorDefinedByBean() { |
||||
loadConfig(SockJsProxylessSecurityConfig.class); |
||||
MessageChannel messageChannel = clientInboundChannel(); |
||||
SecurityContextChannelInterceptor securityContextChannelInterceptor = this.context |
||||
.getBean(SecurityContextChannelInterceptor.class); |
||||
assertThat(((AbstractMessageChannel) messageChannel).getInterceptors()) |
||||
.contains(securityContextChannelInterceptor); |
||||
} |
||||
|
||||
@Test |
||||
public void inboundChannelSecurityDefinedByBean() { |
||||
loadConfig(SockJsProxylessSecurityConfig.class); |
||||
MessageChannel messageChannel = clientInboundChannel(); |
||||
ChannelSecurityInterceptor inboundChannelSecurity = this.context.getBean(ChannelSecurityInterceptor.class); |
||||
assertThat(((AbstractMessageChannel) messageChannel).getInterceptors()).contains(inboundChannelSecurity); |
||||
} |
||||
|
||||
private void assertHandshake(HttpServletRequest request) { |
||||
TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class); |
||||
assertThatCsrfToken(handshakeHandler.attributes.get(CsrfToken.class.getName())).isEqualTo(this.token); |
||||
assertThat(handshakeHandler.attributes).containsEntry(this.sessionAttr, |
||||
request.getSession().getAttribute(this.sessionAttr)); |
||||
} |
||||
|
||||
private HttpRequestHandler handler(HttpServletRequest request) throws Exception { |
||||
HandlerMapping handlerMapping = this.context.getBean(HandlerMapping.class); |
||||
return (HttpRequestHandler) handlerMapping.getHandler(request).getHandler(); |
||||
} |
||||
|
||||
private MockHttpServletRequest websocketHttpRequest(String mapping) { |
||||
MockHttpServletRequest request = sockjsHttpRequest(mapping); |
||||
request.setRequestURI(mapping); |
||||
return request; |
||||
} |
||||
|
||||
private MockHttpServletRequest sockjsHttpRequest(String mapping) { |
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); |
||||
request.setMethod("GET"); |
||||
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/289/tpyx6mde/websocket"); |
||||
request.setRequestURI(mapping + "/289/tpyx6mde/websocket"); |
||||
request.getSession().setAttribute(this.sessionAttr, "sessionValue"); |
||||
request.setAttribute(DeferredCsrfToken.class.getName(), new TestDeferredCsrfToken(this.token)); |
||||
return request; |
||||
} |
||||
|
||||
private Message<String> message(String destination) { |
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(); |
||||
return message(headers, destination); |
||||
} |
||||
|
||||
private Message<String> message(SimpMessageHeaderAccessor headers, String destination) { |
||||
headers.setSessionId("123"); |
||||
headers.setSessionAttributes(new HashMap<>()); |
||||
if (destination != null) { |
||||
headers.setDestination(destination); |
||||
} |
||||
if (this.messageUser != null) { |
||||
headers.setUser(this.messageUser); |
||||
} |
||||
return new GenericMessage<>("hi", headers.getMessageHeaders()); |
||||
} |
||||
|
||||
private MessageChannel clientInboundChannel() { |
||||
return this.context.getBean("clientInboundChannel", MessageChannel.class); |
||||
} |
||||
|
||||
private void loadConfig(Class<?>... configs) { |
||||
this.context = new AnnotationConfigWebApplicationContext(); |
||||
this.context.register(configs); |
||||
this.context.setServletConfig(new MockServletConfig()); |
||||
this.context.refresh(); |
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebSocketMessageBroker |
||||
@Import(SyncExecutorConfig.class) |
||||
static class MsmsRegistryCustomPatternMatcherConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { |
||||
|
||||
// @formatter:off
|
||||
@Override |
||||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
registry |
||||
.addEndpoint("/other") |
||||
.setHandshakeHandler(testHandshakeHandler()); |
||||
} |
||||
// @formatter:on
|
||||
// @formatter:off
|
||||
@Override |
||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { |
||||
messages |
||||
.simpDestMatchers("/app/a.*").permitAll() |
||||
.anyMessage().denyAll(); |
||||
} |
||||
// @formatter:on
|
||||
@Override |
||||
public void configureMessageBroker(MessageBrokerRegistry registry) { |
||||
registry.setPathMatcher(new AntPathMatcher(".")); |
||||
registry.enableSimpleBroker("/queue/", "/topic/"); |
||||
registry.setApplicationDestinationPrefixes("/app"); |
||||
} |
||||
|
||||
@Bean |
||||
TestHandshakeHandler testHandshakeHandler() { |
||||
return new TestHandshakeHandler(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebSocketMessageBroker |
||||
@Import(SyncExecutorConfig.class) |
||||
static class OverrideMsmsRegistryCustomPatternMatcherConfig |
||||
extends AbstractSecurityWebSocketMessageBrokerConfigurer { |
||||
|
||||
// @formatter:off
|
||||
@Override |
||||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
registry |
||||
.addEndpoint("/other") |
||||
.setHandshakeHandler(testHandshakeHandler()); |
||||
} |
||||
// @formatter:on
|
||||
// @formatter:off
|
||||
@Override |
||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { |
||||
messages |
||||
.simpDestPathMatcher(new AntPathMatcher()) |
||||
.simpDestMatchers("/app/a/*").permitAll() |
||||
.anyMessage().denyAll(); |
||||
} |
||||
// @formatter:on
|
||||
@Override |
||||
public void configureMessageBroker(MessageBrokerRegistry registry) { |
||||
registry.setPathMatcher(new AntPathMatcher(".")); |
||||
registry.enableSimpleBroker("/queue/", "/topic/"); |
||||
registry.setApplicationDestinationPrefixes("/app"); |
||||
} |
||||
|
||||
@Bean |
||||
TestHandshakeHandler testHandshakeHandler() { |
||||
return new TestHandshakeHandler(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebSocketMessageBroker |
||||
@Import(SyncExecutorConfig.class) |
||||
static class DefaultPatternMatcherConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { |
||||
|
||||
// @formatter:off
|
||||
@Override |
||||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
registry |
||||
.addEndpoint("/other") |
||||
.setHandshakeHandler(testHandshakeHandler()); |
||||
} |
||||
// @formatter:on
|
||||
// @formatter:off
|
||||
@Override |
||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { |
||||
messages |
||||
.simpDestMatchers("/app/a/*").permitAll() |
||||
.anyMessage().denyAll(); |
||||
} |
||||
// @formatter:on
|
||||
@Override |
||||
public void configureMessageBroker(MessageBrokerRegistry registry) { |
||||
registry.enableSimpleBroker("/queue/", "/topic/"); |
||||
registry.setApplicationDestinationPrefixes("/app"); |
||||
} |
||||
|
||||
@Bean |
||||
TestHandshakeHandler testHandshakeHandler() { |
||||
return new TestHandshakeHandler(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebSocketMessageBroker |
||||
@Import(SyncExecutorConfig.class) |
||||
static class CustomExpressionConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { |
||||
|
||||
// @formatter:off
|
||||
@Override |
||||
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 |
||||
static 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(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Controller |
||||
static class MyController { |
||||
|
||||
String authenticationPrincipal; |
||||
|
||||
MyCustomArgument myCustomArgument; |
||||
|
||||
@MessageMapping("/authentication") |
||||
public void authentication(@AuthenticationPrincipal String un) { |
||||
this.authenticationPrincipal = un; |
||||
} |
||||
|
||||
@MessageMapping("/myCustom") |
||||
public void myCustom(MyCustomArgument myCustomArgument) { |
||||
this.myCustomArgument = myCustomArgument; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class MyCustomArgument { |
||||
|
||||
MyCustomArgument(String notDefaultConstr) { |
||||
} |
||||
|
||||
} |
||||
|
||||
static class MyCustomArgumentResolver implements HandlerMethodArgumentResolver { |
||||
|
||||
@Override |
||||
public boolean supportsParameter(MethodParameter parameter) { |
||||
return parameter.getParameterType().isAssignableFrom(MyCustomArgument.class); |
||||
} |
||||
|
||||
@Override |
||||
public Object resolveArgument(MethodParameter parameter, Message<?> message) { |
||||
return new MyCustomArgument(""); |
||||
} |
||||
|
||||
} |
||||
|
||||
static class TestHandshakeHandler implements HandshakeHandler { |
||||
|
||||
Map<String, Object> attributes; |
||||
|
||||
@Override |
||||
public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, |
||||
Map<String, Object> attributes) throws HandshakeFailureException { |
||||
this.attributes = attributes; |
||||
if (wsHandler instanceof SockJsWebSocketHandler) { |
||||
// work around SPR-12716
|
||||
SockJsWebSocketHandler sockJs = (SockJsWebSocketHandler) wsHandler; |
||||
WebSocketServerSockJsSession session = (WebSocketServerSockJsSession) ReflectionTestUtils |
||||
.getField(sockJs, "sockJsSession"); |
||||
this.attributes = session.getAttributes(); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebSocketMessageBroker |
||||
@Import(SyncExecutorConfig.class) |
||||
static class SockJsSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { |
||||
|
||||
@Override |
||||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
// @formatter:off
|
||||
registry.addEndpoint("/other").setHandshakeHandler(testHandshakeHandler()) |
||||
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); |
||||
registry.addEndpoint("/chat").setHandshakeHandler(testHandshakeHandler()) |
||||
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
// @formatter:off
|
||||
@Override |
||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { |
||||
messages |
||||
.simpDestMatchers("/permitAll/**").permitAll() |
||||
.simpDestMatchers("/beanResolver/**").access("@security.check()") |
||||
.anyMessage().denyAll(); |
||||
} |
||||
// @formatter:on
|
||||
@Override |
||||
public void configureMessageBroker(MessageBrokerRegistry registry) { |
||||
registry.enableSimpleBroker("/queue/", "/topic/"); |
||||
registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll"); |
||||
} |
||||
|
||||
@Bean |
||||
public MyController myController() { |
||||
return new MyController(); |
||||
} |
||||
|
||||
@Bean |
||||
public TestHandshakeHandler testHandshakeHandler() { |
||||
return new TestHandshakeHandler(); |
||||
} |
||||
|
||||
@Bean |
||||
public SecurityCheck security() { |
||||
return new SecurityCheck(); |
||||
} |
||||
|
||||
static class SecurityCheck { |
||||
|
||||
private boolean check; |
||||
|
||||
public boolean check() { |
||||
this.check = !this.check; |
||||
return this.check; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebSocketMessageBroker |
||||
@Import(SyncExecutorConfig.class) |
||||
static class NoInboundSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { |
||||
|
||||
@Override |
||||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
// @formatter:off
|
||||
registry.addEndpoint("/other") |
||||
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); |
||||
registry.addEndpoint("/chat") |
||||
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Override |
||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { |
||||
} |
||||
|
||||
@Override |
||||
public void configureMessageBroker(MessageBrokerRegistry registry) { |
||||
registry.enableSimpleBroker("/queue/", "/topic/"); |
||||
registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll"); |
||||
} |
||||
|
||||
@Bean |
||||
public MyController myController() { |
||||
return new MyController(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
static class CsrfDisabledSockJsSecurityConfig extends SockJsSecurityConfig { |
||||
|
||||
@Override |
||||
protected boolean sameOriginDisabled() { |
||||
return true; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebSocketMessageBroker |
||||
@Import(SyncExecutorConfig.class) |
||||
static class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { |
||||
|
||||
@Override |
||||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
// @formatter:off
|
||||
registry.addEndpoint("/websocket") |
||||
.setHandshakeHandler(testHandshakeHandler()) |
||||
.addInterceptors(new HttpSessionHandshakeInterceptor()); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Override |
||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { |
||||
// @formatter:off
|
||||
messages |
||||
.simpDestMatchers("/permitAll/**").permitAll() |
||||
.simpDestMatchers("/customExpression/**").access("denyRob") |
||||
.anyMessage().denyAll(); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Bean |
||||
public TestHandshakeHandler testHandshakeHandler() { |
||||
return new TestHandshakeHandler(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
@EnableWebSocketMessageBroker |
||||
@Import(SyncExecutorConfig.class) |
||||
static class SockJsProxylessSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { |
||||
|
||||
private ApplicationContext context; |
||||
|
||||
@Override |
||||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
// @formatter:off
|
||||
registry.addEndpoint("/chat") |
||||
.setHandshakeHandler(this.context.getBean(TestHandshakeHandler.class)) |
||||
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Autowired |
||||
public void setContext(ApplicationContext context) { |
||||
this.context = context; |
||||
} |
||||
|
||||
// @formatter:off
|
||||
@Override |
||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { |
||||
messages |
||||
.anyMessage().denyAll(); |
||||
} |
||||
// @formatter:on
|
||||
@Bean |
||||
public TestHandshakeHandler testHandshakeHandler() { |
||||
return new TestHandshakeHandler(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
static class SyncExecutorConfig { |
||||
|
||||
@Bean |
||||
public static SyncExecutorSubscribableChannelPostProcessor postProcessor() { |
||||
return new SyncExecutorSubscribableChannelPostProcessor(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue