mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-01 08:12:14 +00:00
Remove AbstractSecurityWebSocketMessageBrokerConfigurer
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
This commit is contained in:
parent
a74ce06dae
commit
e686ac6b11
@ -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 @@
|
|||||||
/*
|
|
||||||
* 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 @@
|
|||||||
/*
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -69,7 +69,6 @@ import org.springframework.security.authorization.AuthorizationDecision;
|
|||||||
import org.springframework.security.authorization.AuthorizationManager;
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
|
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
|
|
||||||
import org.springframework.security.config.observation.SecurityObservationSettings;
|
import org.springframework.security.config.observation.SecurityObservationSettings;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
|
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
|
||||||
@ -878,37 +877,6 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSocketSecurity
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
@Import(SyncExecutorConfig.class)
|
|
||||||
static class UsingLegacyConfigurerConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
// @formatter:off
|
|
||||||
registry.addEndpoint("/websocket")
|
|
||||||
.setHandshakeHandler(testHandshakeHandler())
|
|
||||||
.addInterceptors(new HttpSessionHandshakeInterceptor());
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
|
|
||||||
// @formatter:off
|
|
||||||
messages
|
|
||||||
.simpDestMatchers("/permitAll/**").permitAll()
|
|
||||||
.anyMessage().denyAll();
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
TestHandshakeHandler testHandshakeHandler() {
|
|
||||||
return new TestHandshakeHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@EnableWebSocketSecurity
|
@EnableWebSocketSecurity
|
||||||
@EnableWebSocketMessageBroker
|
@EnableWebSocketMessageBroker
|
||||||
|
@ -492,43 +492,6 @@ Xml::
|
|||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
On the other hand, if you are using the <<legacy-websocket-configuration,legacy `AbstractSecurityWebSocketMessageBrokerConfigurer`>> and you want to allow other domains to access your site, you can disable Spring Security's protection.
|
|
||||||
For example, in Java Configuration you can use the following:
|
|
||||||
|
|
||||||
[tabs]
|
|
||||||
======
|
|
||||||
Java::
|
|
||||||
+
|
|
||||||
[source,java,role="primary"]
|
|
||||||
----
|
|
||||||
@Configuration
|
|
||||||
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean sameOriginDisabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
Kotlin::
|
|
||||||
+
|
|
||||||
[source,kotlin,role="secondary"]
|
|
||||||
----
|
|
||||||
@Configuration
|
|
||||||
open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() {
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
override fun sameOriginDisabled(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----
|
|
||||||
======
|
|
||||||
|
|
||||||
[[websocket-expression-handler]]
|
[[websocket-expression-handler]]
|
||||||
=== Custom Expression Handler
|
=== Custom Expression Handler
|
||||||
|
|
||||||
@ -742,50 +705,3 @@ If we use XML-based configuration, we can use thexref:servlet/appendix/namespace
|
|||||||
</b:constructor-arg>
|
</b:constructor-arg>
|
||||||
</b:bean>
|
</b:bean>
|
||||||
----
|
----
|
||||||
|
|
||||||
[[legacy-websocket-configuration]]
|
|
||||||
== Legacy WebSocket Configuration
|
|
||||||
|
|
||||||
Before Spring Security 5.8, the way to configure messaging authorization using Java Configuration, was to extend the `AbstractSecurityWebSocketMessageBrokerConfigurer` and configure the `MessageSecurityMetadataSourceRegistry`.
|
|
||||||
For example:
|
|
||||||
|
|
||||||
[tabs]
|
|
||||||
======
|
|
||||||
Java::
|
|
||||||
+
|
|
||||||
[source,java,role="primary"]
|
|
||||||
----
|
|
||||||
@Configuration
|
|
||||||
public class WebSocketSecurityConfig
|
|
||||||
extends AbstractSecurityWebSocketMessageBrokerConfigurer { // <1> <2>
|
|
||||||
|
|
||||||
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
|
|
||||||
messages
|
|
||||||
.simpDestMatchers("/user/**").authenticated() // <3>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
Kotlin::
|
|
||||||
+
|
|
||||||
[source,kotlin,role="secondary"]
|
|
||||||
----
|
|
||||||
@Configuration
|
|
||||||
open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() { // <1> <2>
|
|
||||||
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
|
|
||||||
messages.simpDestMatchers("/user/**").authenticated() // <3>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----
|
|
||||||
======
|
|
||||||
|
|
||||||
This will ensure that:
|
|
||||||
|
|
||||||
<1> Any inbound CONNECT message requires a valid CSRF token to enforce <<websocket-sameorigin,Same Origin Policy>>
|
|
||||||
<2> The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
|
|
||||||
<3> Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in <<websocket-authorization>>
|
|
||||||
|
|
||||||
Using the legacy configuration is helpful in the event that you have a custom `SecurityExpressionHandler` that extends `AbstractSecurityExpressionHandler` and overrides `createEvaluationContextInternal` or `createSecurityExpressionRoot`.
|
|
||||||
In order to defer `Authorization` lookup, the new `AuthorizationManager` API does not invoke these when evaluating expressions.
|
|
||||||
|
|
||||||
If you are using XML, you can use the legacy APIs simply by not using the `use-authorization-manager` element or setting it to `false`.
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
<!-- Method Visibility that we can't reduce -->
|
<!-- Method Visibility that we can't reduce -->
|
||||||
<suppress files="AbstractAclVoterTests\.java" checks="SpringMethodVisibility"/>
|
<suppress files="AbstractAclVoterTests\.java" checks="SpringMethodVisibility"/>
|
||||||
<suppress files="AbstractSecurityWebSocketMessageBrokerConfigurerTests\.java" checks="SpringMethodVisibility"/>
|
|
||||||
<suppress files="AnnotationParameterNameDiscovererTests\.java" checks="SpringMethodVisibility"/>
|
<suppress files="AnnotationParameterNameDiscovererTests\.java" checks="SpringMethodVisibility"/>
|
||||||
<suppress files="AnnotationSecurityAspectTests\.java" checks="SpringMethodVisibility"/>
|
<suppress files="AnnotationSecurityAspectTests\.java" checks="SpringMethodVisibility"/>
|
||||||
<suppress files="AuthenticationPrincipalArgumentResolverTests\.java" checks="SpringMethodVisibility"/>
|
<suppress files="AuthenticationPrincipalArgumentResolverTests\.java" checks="SpringMethodVisibility"/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user