Add SecurityContextHolderStrategy Java Configuration for Method Security

Issue gh-11061
This commit is contained in:
Josh Cummings
2022-06-21 17:11:43 -06:00
parent 25c74896d1
commit da57bac061
7 changed files with 142 additions and 13 deletions

View File

@@ -50,19 +50,24 @@ import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.test.SpringTestParentApplicationContextExecutionListener;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.test.context.support.WithAnonymousUser;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -73,7 +78,9 @@ import static org.mockito.Mockito.verify;
* @author Josh Cummings
*/
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
@SecurityTestExecutionListeners
@ContextConfiguration(classes = SecurityContextChangedListenerConfig.class)
@TestExecutionListeners(listeners = { WithSecurityContextTestExecutionListener.class,
SpringTestParentApplicationContextExecutionListener.class })
public class PrePostMethodSecurityConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@@ -137,6 +144,8 @@ public class PrePostMethodSecurityConfigurationTests {
this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire();
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
.withMessage("Access Denied");
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
verify(strategy, atLeastOnce()).getContext();
}
@WithMockUser
@@ -162,6 +171,15 @@ public class PrePostMethodSecurityConfigurationTests {
this.methodSecurityService.preAuthorizeAdmin();
}
@WithMockUser(roles = "ADMIN")
@Test
public void preAuthorizeAdminWhenSecurityContextHolderStrategyThenUses() {
this.spring.register(MethodSecurityServiceConfig.class).autowire();
this.methodSecurityService.preAuthorizeAdmin();
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
verify(strategy, atLeastOnce()).getContext();
}
@WithMockUser(authorities = "PREFIX_ADMIN")
@Test
public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() {
@@ -285,6 +303,8 @@ public class PrePostMethodSecurityConfigurationTests {
this.spring.register(BusinessServiceConfig.class).autowire();
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.businessService::rolesAllowedUser)
.withMessage("Access Denied");
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
verify(strategy, atLeastOnce()).getContext();
}
@WithMockUser
@@ -480,12 +500,15 @@ public class PrePostMethodSecurityConfigurationTests {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor customBeforeAdvice() {
Advisor customBeforeAdvice(SecurityContextHolderStrategy strategy) {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*MethodSecurityServiceImpl.*securedUser");
AuthorizationManager<MethodInvocation> authorizationManager = (a,
o) -> new AuthorizationDecision("bob".equals(a.get().getName()));
return new AuthorizationManagerBeforeMethodInterceptor(pointcut, authorizationManager);
AuthorizationManagerBeforeMethodInterceptor before = new AuthorizationManagerBeforeMethodInterceptor(
pointcut, authorizationManager);
before.setSecurityContextHolderStrategy(strategy);
return before;
}
}
@@ -495,11 +518,11 @@ public class PrePostMethodSecurityConfigurationTests {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor customAfterAdvice() {
Advisor customAfterAdvice(SecurityContextHolderStrategy strategy) {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*MethodSecurityServiceImpl.*securedUser");
MethodInterceptor interceptor = (mi) -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Authentication auth = strategy.getContext().getAuthentication();
if ("bob".equals(auth.getName())) {
return "granted";
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.security.config.test;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -56,6 +57,8 @@ public class SpringTestContext implements Closeable {
private List<Filter> filters = new ArrayList<>();
private List<Consumer<ConfigurableWebApplicationContext>> postProcessors = new ArrayList<>();
public SpringTestContext(Object test) {
setTest(test);
}
@@ -104,6 +107,11 @@ public class SpringTestContext implements Closeable {
return this;
}
public SpringTestContext postProcessor(Consumer<ConfigurableWebApplicationContext> contextConsumer) {
this.postProcessors.add(contextConsumer);
return this;
}
public SpringTestContext mockMvcAfterSpringSecurityOk() {
return addFilter(new OncePerRequestFilter() {
@Override
@@ -131,6 +139,9 @@ public class SpringTestContext implements Closeable {
public void autowire() {
this.context.setServletContext(new MockServletContext());
this.context.setServletConfig(new MockServletConfig());
for (Consumer<ConfigurableWebApplicationContext> postProcessor : this.postProcessors) {
postProcessor.accept(this.context);
}
this.context.refresh();
if (this.context.containsBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN)) {
// @formatter:off

View File

@@ -0,0 +1,48 @@
/*
* 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.test;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
public class SpringTestParentApplicationContextExecutionListener implements TestExecutionListener {
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
ApplicationContext parent = testContext.getApplicationContext();
Object testInstance = testContext.getTestInstance();
getContexts(testInstance).forEach((springTestContext) -> springTestContext
.postProcessor((applicationContext) -> applicationContext.setParent(parent)));
}
private static List<SpringTestContext> getContexts(Object test) throws IllegalAccessException {
Field[] declaredFields = test.getClass().getDeclaredFields();
List<SpringTestContext> result = new ArrayList<>();
for (Field field : declaredFields) {
if (SpringTestContext.class.isAssignableFrom(field.getType())) {
result.add((SpringTestContext) field.get(test));
}
}
return result;
}
}