Replace bean method calls with injection

This is so that our configuration classes do not rely on CGLIB to proxy bean methods.

Fixes gh-6818
This commit is contained in:
Eleftheria Stein 2019-05-09 12:15:42 -04:00 committed by Rob Winch
parent 97a01260f1
commit 06d3b60947
12 changed files with 337 additions and 36 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -107,8 +107,7 @@ public class AuthenticationConfiguration {
if (this.authenticationManagerInitialized) { if (this.authenticationManagerInitialized) {
return this.authenticationManager; return this.authenticationManager;
} }
AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder( AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
this.objectPostProcessor, this.applicationContext);
if (this.buildingAuthenticationManager.getAndSet(true)) { if (this.buildingAuthenticationManager.getAndSet(true)) {
return new AuthenticationManagerDelegator(authBuilder); return new AuthenticationManagerDelegator(authBuilder);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -29,10 +29,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
@ -110,7 +107,6 @@ public class GlobalMethodSecurityConfiguration
* <li>{@link #accessDecisionManager()}</li> * <li>{@link #accessDecisionManager()}</li>
* <li>{@link #afterInvocationManager()}</li> * <li>{@link #afterInvocationManager()}</li>
* <li>{@link #authenticationManager()}</li> * <li>{@link #authenticationManager()}</li>
* <li>{@link #methodSecurityMetadataSource()}</li>
* <li>{@link #runAsManager()}</li> * <li>{@link #runAsManager()}</li>
* *
* </ul> * </ul>
@ -119,19 +115,19 @@ public class GlobalMethodSecurityConfiguration
* Subclasses can override this method to provide a different * Subclasses can override this method to provide a different
* {@link MethodInterceptor}. * {@link MethodInterceptor}.
* </p> * </p>
* @param methodSecurityMetadataSource the default {@link MethodSecurityMetadataSource}.
* *
* @return the {@link MethodInterceptor}. * @return the {@link MethodInterceptor}.
* @throws Exception
*/ */
@Bean @Bean
public MethodInterceptor methodSecurityInterceptor() throws Exception { public MethodInterceptor methodSecurityInterceptor(MethodSecurityMetadataSource methodSecurityMetadataSource) {
this.methodSecurityInterceptor = isAspectJ() this.methodSecurityInterceptor = isAspectJ()
? new AspectJMethodSecurityInterceptor() ? new AspectJMethodSecurityInterceptor()
: new MethodSecurityInterceptor(); : new MethodSecurityInterceptor();
methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager()); methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
methodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager()); methodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager());
methodSecurityInterceptor methodSecurityInterceptor
.setSecurityMetadataSource(methodSecurityMetadataSource()); .setSecurityMetadataSource(methodSecurityMetadataSource);
RunAsManager runAsManager = runAsManager(); RunAsManager runAsManager = runAsManager();
if (runAsManager != null) { if (runAsManager != null) {
methodSecurityInterceptor.setRunAsManager(runAsManager); methodSecurityInterceptor.setRunAsManager(runAsManager);
@ -197,8 +193,8 @@ public class GlobalMethodSecurityConfiguration
/** /**
* Provide a custom {@link AfterInvocationManager} for the default implementation of * Provide a custom {@link AfterInvocationManager} for the default implementation of
* {@link #methodSecurityInterceptor()}. The default is null if pre post is not * {@link #methodSecurityInterceptor(MethodSecurityMetadataSource)}. The default is null
* enabled. Otherwise, it returns a {@link AfterInvocationProviderManager}. * if pre post is not enabled. Otherwise, it returns a {@link AfterInvocationProviderManager}.
* *
* <p> * <p>
* Subclasses should override this method to provide a custom * Subclasses should override this method to provide a custom
@ -224,7 +220,7 @@ public class GlobalMethodSecurityConfiguration
/** /**
* Provide a custom {@link RunAsManager} for the default implementation of * Provide a custom {@link RunAsManager} for the default implementation of
* {@link #methodSecurityInterceptor()}. The default is null. * {@link #methodSecurityInterceptor(MethodSecurityMetadataSource)}. The default is null.
* *
* @return the {@link RunAsManager} to use * @return the {@link RunAsManager} to use
*/ */

View File

@ -43,7 +43,7 @@ import org.springframework.web.reactive.result.method.annotation.ArgumentResolve
* @since 5.0 * @since 5.0
*/ */
@Configuration @Configuration
class ServerHttpSecurityConfiguration implements WebFluxConfigurer { class ServerHttpSecurityConfiguration {
private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.reactive.HttpSecurityConfiguration."; private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.reactive.HttpSecurityConfiguration.";
private static final String HTTPSECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity"; private static final String HTTPSECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity";
@ -85,9 +85,15 @@ class ServerHttpSecurityConfiguration implements WebFluxConfigurer {
this.userDetailsPasswordService = userDetailsPasswordService; this.userDetailsPasswordService = userDetailsPasswordService;
} }
@Bean
public WebFluxConfigurer authenticationPrincipalArgumentResolverConfigurer(
AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver) {
return new WebFluxConfigurer() {
@Override @Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
configurer.addCustomResolver(authenticationPrincipalArgumentResolver()); configurer.addCustomResolver(authenticationPrincipalArgumentResolver);
}
};
} }
@Bean @Bean
@ -110,7 +116,6 @@ class ServerHttpSecurityConfiguration implements WebFluxConfigurer {
return resolver; return resolver;
} }
@Bean(HTTPSECURITY_BEAN_NAME) @Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype") @Scope("prototype")
public ServerHttpSecurity httpSecurity() { public ServerHttpSecurity httpSecurity() {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -103,10 +103,10 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends
@Override @Override
public final void configureClientInboundChannel(ChannelRegistration registration) { public final void configureClientInboundChannel(ChannelRegistration registration) {
ChannelSecurityInterceptor inboundChannelSecurity = inboundChannelSecurity(); ChannelSecurityInterceptor inboundChannelSecurity = context.getBean(ChannelSecurityInterceptor.class);
registration.setInterceptors(securityContextChannelInterceptor()); registration.setInterceptors(context.getBean(SecurityContextChannelInterceptor.class));
if (!sameOriginDisabled()) { if (!sameOriginDisabled()) {
registration.setInterceptors(csrfChannelInterceptor()); registration.setInterceptors(context.getBean(CsrfChannelInterceptor.class));
} }
if (inboundRegistry.containsMapping()) { if (inboundRegistry.containsMapping()) {
registration.setInterceptors(inboundChannelSecurity); registration.setInterceptors(inboundChannelSecurity);
@ -153,9 +153,9 @@ public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends
} }
@Bean @Bean
public ChannelSecurityInterceptor inboundChannelSecurity() { public ChannelSecurityInterceptor inboundChannelSecurity(MessageSecurityMetadataSource messageSecurityMetadataSource) {
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor( ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor(
inboundMessageSecurityMetadataSource()); messageSecurityMetadataSource);
MessageExpressionVoter<Object> voter = new MessageExpressionVoter<>(); MessageExpressionVoter<Object> voter = new MessageExpressionVoter<>();
voter.setExpressionHandler(getMessageExpressionHandler()); voter.setExpressionHandler(getMessageExpressionHandler());

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -34,6 +34,7 @@ import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.AlreadyBuiltException;
import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration; import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
@ -64,9 +65,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class AuthenticationConfigurationTests { public class AuthenticationConfigurationTests {
@ -530,4 +529,20 @@ public class AuthenticationConfigurationTests {
} }
} }
@Test
public void getAuthenticationManagerWhenAuthenticationConfigurationSubclassedThenBuildsUsingBean()
throws Exception {
this.spring.register(AuthenticationConfigurationSubclass.class).autowire();
AuthenticationManagerBuilder ap = this.spring.getContext().getBean(AuthenticationManagerBuilder.class);
this.spring.getContext().getBean(AuthenticationConfiguration.class).getAuthenticationManager();
assertThatThrownBy(ap::build)
.isInstanceOf(AlreadyBuiltException.class);
}
@Configuration(proxyBeanMethods = false)
static class AuthenticationConfigurationSubclass extends AuthenticationConfiguration {
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
@ -553,4 +554,20 @@ public class GlobalMethodSecurityConfigurationTests {
public void emptyPrefixRoleUser() {} public void emptyPrefixRoleUser() {}
} }
} }
@Test
public void methodSecurityInterceptorUsesMetadataSourceBeanWhenProxyingDisabled() {
this.spring.register(CustomMetadataSourceProxylessConfig.class).autowire();
MethodSecurityInterceptor methodInterceptor =
(MethodSecurityInterceptor) this.spring.getContext().getBean(MethodInterceptor.class);
MethodSecurityMetadataSource methodSecurityMetadataSource =
this.spring.getContext().getBean(MethodSecurityMetadataSource.class);
assertThat(methodInterceptor.getSecurityMetadataSource()).isSameAs(methodSecurityMetadataSource);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration(proxyBeanMethods = false)
public static class CustomMetadataSourceProxylessConfig extends GlobalMethodSecurityConfiguration {
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -163,8 +163,8 @@ public class NamespaceGlobalMethodSecurityTests {
public static class CustomAuthenticationConfig extends GlobalMethodSecurityConfiguration { public static class CustomAuthenticationConfig extends GlobalMethodSecurityConfiguration {
@Override @Override
public MethodInterceptor methodSecurityInterceptor() throws Exception { public MethodInterceptor methodSecurityInterceptor(MethodSecurityMetadataSource methodSecurityMetadataSource) {
MethodInterceptor interceptor = super.methodSecurityInterceptor(); MethodInterceptor interceptor = super.methodSecurityInterceptor(methodSecurityMetadataSource);
((MethodSecurityInterceptor) interceptor).setAlwaysReauthenticate(true); ((MethodSecurityInterceptor) interceptor).setAlwaysReauthenticate(true);
return interceptor; return interceptor;
} }

View File

@ -88,4 +88,24 @@ public class ReactiveMethodSecurityConfigurationTests {
} }
} }
@Test
public void rolePrefixWithGrantedAuthorityDefaultsAndSubclassWithProxyingDisabled() {
this.spring.register(SubclassConfig.class).autowire();
TestingAuthenticationToken authentication = new TestingAuthenticationToken(
"principal", "credential", "ROLE_ABC");
MockMethodInvocation methodInvocation = mock(MockMethodInvocation.class);
EvaluationContext context = this.methodSecurityExpressionHandler
.createEvaluationContext(authentication, methodInvocation);
SecurityExpressionRoot root = (SecurityExpressionRoot) context.getRootObject()
.getValue();
assertThat(root.hasRole("ROLE_ABC")).isTrue();
assertThat(root.hasRole("ABC")).isTrue();
}
@Configuration(proxyBeanMethods = false)
static class SubclassConfig extends ReactiveMethodSecurityConfiguration {
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -34,6 +34,7 @@ import org.springframework.security.access.expression.AbstractSecurityExpression
import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.config.test.SpringTestRule;
@ -403,4 +404,52 @@ public class WebSecurityConfigurationTests {
Method method = ClassUtils.getMethod(WebSecurityConfiguration.class, "delegatingApplicationListener", null); Method method = ClassUtils.getMethod(WebSecurityConfiguration.class, "delegatingApplicationListener", null);
assertThat(Modifier.isStatic(method.getModifiers())).isTrue(); assertThat(Modifier.isStatic(method.getModifiers())).isTrue();
} }
@Test
public void loadConfigWhenProxyingDisabledAndSubclassThenFilterChainsCreated() {
this.spring.register(GlobalAuthenticationWebSecurityConfigurerAdaptersConfig.class, SubclassConfig.class).autowire();
FilterChainProxy filterChainProxy = this.spring.getContext().getBean(FilterChainProxy.class);
List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();
assertThat(filterChains).hasSize(4);
}
@Configuration(proxyBeanMethods = false)
static class SubclassConfig extends WebSecurityConfiguration {
}
@Import(AuthenticationTestConfiguration.class)
@EnableGlobalAuthentication
static class GlobalAuthenticationWebSecurityConfigurerAdaptersConfig {
@Configuration
@Order(1)
static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/ignore1", "/ignore2");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/anonymous/**")
.authorizeRequests()
.anyRequest().anonymous();
}
}
@Configuration
static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
}
}
}
} }

View File

@ -0,0 +1,58 @@
/*
* Copyright 2002-2019 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.reactive;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ServerHttpSecurityConfiguration}.
*
* @author Eleftheria Stein
*/
public class ServerHttpSecurityConfigurationTest {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Test
public void loadConfigWhenReactiveUserDetailsServiceConfiguredThenServerHttpSecurityExists() {
this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class).autowire();
ServerHttpSecurity serverHttpSecurity = this.spring.getContext().getBean(ServerHttpSecurity.class);
assertThat(serverHttpSecurity).isNotNull();
}
@Test
public void loadConfigWhenProxyingDisabledAndSubclassThenServerHttpSecurityExists() {
this.spring.register(SubclassConfig.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class).autowire();
ServerHttpSecurity serverHttpSecurity = this.spring.getContext().getBean(ServerHttpSecurity.class);
assertThat(serverHttpSecurity).isNotNull();
}
@Configuration(proxyBeanMethods = false)
static class SubclassConfig extends ServerHttpSecurityConfiguration {
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2002-2019 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.reactive;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
import org.springframework.security.web.server.WebFilterChainProxy;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link WebFluxSecurityConfiguration}.
*
* @author Eleftheria Stein
*/
public class WebFluxSecurityConfigurationTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Test
public void loadConfigWhenReactiveUserDetailsServiceConfiguredThenWebFilterChainProxyExists() {
this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfiguration.class).autowire();
WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class);
assertThat(webFilterChainProxy).isNotNull();
}
@Test
public void loadConfigWhenProxyingDisabledAndSubclassThenWebFilterChainProxyExists() {
this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class,
WebFluxSecurityConfigurationTests.SubclassConfig.class).autowire();
WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class);
assertThat(webFilterChainProxy).isNotNull();
}
@Configuration(proxyBeanMethods = false)
static class SubclassConfig extends WebFluxSecurityConfiguration {
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,6 +26,8 @@ import javax.servlet.http.HttpServletRequest;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -40,6 +42,7 @@ import org.springframework.messaging.handler.invocation.HandlerMethodArgumentRes
import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.support.AbstractMessageChannel;
import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.GenericMessage;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
@ -53,6 +56,10 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler; import org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler;
import org.springframework.security.messaging.access.expression.MessageSecurityExpressionRoot; 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.CsrfToken;
import org.springframework.security.web.csrf.DefaultCsrfToken; import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.csrf.MissingCsrfTokenException; import org.springframework.security.web.csrf.MissingCsrfTokenException;
@ -199,6 +206,16 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
messageChannel.send(message); messageChannel.send(message);
} }
@Test
public void csrfProtectionDefinedByBean() {
loadConfig(SockJsProxylessSecurityConfig.class);
MessageChannel messageChannel = clientInboundChannel();
CsrfChannelInterceptor csrfChannelInterceptor = context.getBean(CsrfChannelInterceptor.class);
assertThat(((AbstractMessageChannel) messageChannel).getInterceptors()).contains(csrfChannelInterceptor);
}
@Test @Test
public void messagesConnectUseCsrfTokenHandshakeInterceptor() throws Exception { public void messagesConnectUseCsrfTokenHandshakeInterceptor() throws Exception {
@ -421,6 +438,41 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
} }
} }
@Test
public void channelSecurityInterceptorUsesMetadataSourceBeanWhenProxyingDisabled() {
loadConfig(SockJsProxylessSecurityConfig.class);
ChannelSecurityInterceptor channelSecurityInterceptor = context.getBean(ChannelSecurityInterceptor.class);
MessageSecurityMetadataSource messageSecurityMetadataSource =
context.getBean(MessageSecurityMetadataSource.class);
assertThat(channelSecurityInterceptor.obtainSecurityMetadataSource()).isSameAs(messageSecurityMetadataSource);
}
@Test
public void securityContextChannelInterceptorDefinedByBean() {
loadConfig(SockJsProxylessSecurityConfig.class);
MessageChannel messageChannel = clientInboundChannel();
SecurityContextChannelInterceptor securityContextChannelInterceptor =
context.getBean(SecurityContextChannelInterceptor.class);
assertThat(((AbstractMessageChannel) messageChannel).getInterceptors())
.contains(securityContextChannelInterceptor);
}
@Test
public void inboundChannelSecurityDefinedByBean() {
loadConfig(SockJsProxylessSecurityConfig.class);
MessageChannel messageChannel = clientInboundChannel();
ChannelSecurityInterceptor inboundChannelSecurity = context.getBean(ChannelSecurityInterceptor.class);
assertThat(((AbstractMessageChannel) messageChannel).getInterceptors())
.contains(inboundChannelSecurity);
}
@Configuration @Configuration
@EnableWebSocketMessageBroker @EnableWebSocketMessageBroker
@Import(SyncExecutorConfig.class) @Import(SyncExecutorConfig.class)
@ -706,6 +758,38 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
} }
} }
@Configuration(proxyBeanMethods = false)
@EnableWebSocketMessageBroker
@Import(SyncExecutorConfig.class)
static class SockJsProxylessSecurityConfig extends
AbstractSecurityWebSocketMessageBrokerConfigurer {
private ApplicationContext context;
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat")
.setHandshakeHandler(context.getBean(TestHandshakeHandler.class))
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
}
@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 @Configuration
static class SyncExecutorConfig { static class SyncExecutorConfig {
@Bean @Bean