Defer SecurityContextHolderStrategy Lookup

Due to how early method interceptors are loaded during startup
it's reasonable to consider scenarios where applications are
changing the global security context holder strategy during
startup.

Closes gh-12877
This commit is contained in:
Josh Cummings 2023-11-06 17:08:54 -07:00
parent eff9814d7b
commit 11a21896dd
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
8 changed files with 131 additions and 62 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -51,8 +51,7 @@ import org.springframework.util.Assert;
public final class AuthorizationManagerAfterMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private Supplier<Authentication> authentication = getAuthentication(
SecurityContextHolder.getContextHolderStrategy());
private Supplier<SecurityContextHolderStrategy> securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy;
private final Log logger = LogFactory.getLog(this.getClass());
@ -156,14 +155,14 @@ public final class AuthorizationManagerAfterMethodInterceptor
* @since 5.8
*/
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strategy) {
this.authentication = getAuthentication(strategy);
this.securityContextHolderStrategy = () -> strategy;
}
private void attemptAuthorization(MethodInvocation mi, Object result) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
MethodInvocationResult object = new MethodInvocationResult(mi, result);
AuthorizationDecision decision = this.authorizationManager.check(this.authentication, object);
this.eventPublisher.publishAuthorizationEvent(this.authentication, object, decision);
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, object);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, object, decision);
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
+ this.authorizationManager + " and decision " + decision));
@ -172,15 +171,13 @@ public final class AuthorizationManagerAfterMethodInterceptor
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
}
private Supplier<Authentication> getAuthentication(SecurityContextHolderStrategy strategy) {
return () -> {
Authentication authentication = strategy.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
};
private Authentication getAuthentication() {
Authentication authentication = this.securityContextHolderStrategy.get().getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
}
private static <T> void noPublish(Supplier<Authentication> authentication, T object,

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -56,8 +56,7 @@ import org.springframework.util.Assert;
public final class AuthorizationManagerBeforeMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private Supplier<Authentication> authentication = getAuthentication(
SecurityContextHolder.getContextHolderStrategy());
private Supplier<SecurityContextHolderStrategy> securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy;
private final Log logger = LogFactory.getLog(this.getClass());
@ -202,13 +201,13 @@ public final class AuthorizationManagerBeforeMethodInterceptor
* @since 5.8
*/
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.authentication = getAuthentication(securityContextHolderStrategy);
this.securityContextHolderStrategy = () -> securityContextHolderStrategy;
}
private void attemptAuthorization(MethodInvocation mi) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
AuthorizationDecision decision = this.authorizationManager.check(this.authentication, mi);
this.eventPublisher.publishAuthorizationEvent(this.authentication, mi, decision);
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, mi);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, mi, decision);
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
+ this.authorizationManager + " and decision " + decision));
@ -217,15 +216,13 @@ public final class AuthorizationManagerBeforeMethodInterceptor
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
}
private Supplier<Authentication> getAuthentication(SecurityContextHolderStrategy strategy) {
return () -> {
Authentication authentication = strategy.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
};
private Authentication getAuthentication() {
Authentication authentication = this.securityContextHolderStrategy.get().getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
}
private static <T> void noPublish(Supplier<Authentication> authentication, T object,

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -46,8 +46,7 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
public final class PostFilterAuthorizationMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private Supplier<Authentication> authentication = getAuthentication(
SecurityContextHolder.getContextHolderStrategy());
private Supplier<SecurityContextHolderStrategy> securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy;
private PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry();
@ -108,7 +107,7 @@ public final class PostFilterAuthorizationMethodInterceptor
* @since 5.8
*/
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strategy) {
this.authentication = getAuthentication(strategy);
this.securityContextHolderStrategy = () -> strategy;
}
/**
@ -125,19 +124,17 @@ public final class PostFilterAuthorizationMethodInterceptor
return returnedObject;
}
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);
EvaluationContext ctx = expressionHandler.createEvaluationContext(this::getAuthentication, mi);
return expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
}
private Supplier<Authentication> getAuthentication(SecurityContextHolderStrategy strategy) {
return () -> {
Authentication authentication = strategy.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
};
private Authentication getAuthentication() {
Authentication authentication = this.securityContextHolderStrategy.get().getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -47,8 +47,7 @@ import org.springframework.util.StringUtils;
public final class PreFilterAuthorizationMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private Supplier<Authentication> authentication = getAuthentication(
SecurityContextHolder.getContextHolderStrategy());
private Supplier<SecurityContextHolderStrategy> securityContextHolderStrategy = SecurityContextHolder::getContextHolderStrategy;
private PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry();
@ -109,7 +108,7 @@ public final class PreFilterAuthorizationMethodInterceptor
* @since 5.8
*/
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strategy) {
this.authentication = getAuthentication(strategy);
this.securityContextHolderStrategy = () -> strategy;
}
/**
@ -124,7 +123,7 @@ public final class PreFilterAuthorizationMethodInterceptor
return mi.proceed();
}
MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
EvaluationContext ctx = expressionHandler.createEvaluationContext(this.authentication, mi);
EvaluationContext ctx = expressionHandler.createEvaluationContext(this::getAuthentication, mi);
Object filterTarget = findFilterTarget(attribute.getFilterTarget(), ctx, mi);
expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
return mi.proceed();
@ -150,15 +149,13 @@ public final class PreFilterAuthorizationMethodInterceptor
return filterTarget;
}
private Supplier<Authentication> getAuthentication(SecurityContextHolderStrategy strategy) {
return () -> {
Authentication authentication = strategy.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
};
private Authentication getAuthentication() {
Authentication authentication = this.securityContextHolderStrategy.get().getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -29,6 +29,7 @@ import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -91,6 +92,25 @@ public class AuthorizationManagerAfterMethodInterceptorTests {
verify(strategy).getContext();
}
// gh-12877
@Test
public void afterWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable {
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
Authentication authentication = new TestingAuthenticationToken("john", "password",
AuthorityUtils.createAuthorityList("authority"));
given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
MethodInvocation invocation = mock(MethodInvocation.class);
AuthorizationManager<MethodInvocationResult> authorizationManager = AuthenticatedAuthorizationManager
.authenticated();
AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor(
Pointcut.TRUE, authorizationManager);
SecurityContextHolderStrategy saved = SecurityContextHolder.getContextHolderStrategy();
SecurityContextHolder.setContextHolderStrategy(strategy);
advice.invoke(invocation);
verify(strategy).getContext();
SecurityContextHolder.setContextHolderStrategy(saved);
}
@Test
public void configureWhenAuthorizationEventPublisherIsNullThenIllegalArgument() {
AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor(

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -87,6 +87,24 @@ public class AuthorizationManagerBeforeMethodInterceptorTests {
verify(strategy).getContext();
}
// gh-12877
@Test
public void beforeWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable {
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
Authentication authentication = new TestingAuthenticationToken("john", "password",
AuthorityUtils.createAuthorityList("authority"));
given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
MethodInvocation invocation = mock(MethodInvocation.class);
AuthorizationManager<MethodInvocation> authorizationManager = AuthenticatedAuthorizationManager.authenticated();
AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(
Pointcut.TRUE, authorizationManager);
SecurityContextHolderStrategy saved = SecurityContextHolder.getContextHolderStrategy();
SecurityContextHolder.setContextHolderStrategy(strategy);
advice.invoke(invocation);
verify(strategy).getContext();
SecurityContextHolder.setContextHolderStrategy(saved);
}
@Test
public void configureWhenAuthorizationEventPublisherIsNullThenIllegalArgument() {
AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -147,6 +147,29 @@ public class PostFilterAuthorizationMethodInterceptorTests {
verify(strategy).getContext();
}
// gh-12877
@Test
public void postFilterWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable {
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
Authentication authentication = new TestingAuthenticationToken("john", "password",
AuthorityUtils.createAuthorityList("authority"));
given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
String[] array = { "john", "bob" };
MockMethodInvocation invocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingArrayAuthentication", new Class[] { String[].class }, new Object[] { array }) {
@Override
public Object proceed() {
return array;
}
};
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
SecurityContextHolderStrategy saved = SecurityContextHolder.getContextHolderStrategy();
SecurityContextHolder.setContextHolderStrategy(strategy);
advice.invoke(invocation);
verify(strategy).getContext();
SecurityContextHolder.setContextHolderStrategy(saved);
}
@PostFilter("filterObject == 'john'")
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -204,6 +204,26 @@ public class PreFilterAuthorizationMethodInterceptorTests {
verify(strategy).getContext();
}
// gh-12877
@Test
public void preFilterWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable {
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
Authentication authentication = new TestingAuthenticationToken("john", "password",
AuthorityUtils.createAuthorityList("authority"));
given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
List<String> list = new ArrayList<>();
list.add("john");
list.add("bob");
MockMethodInvocation invocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingArrayFilterAuthentication", new Class[] { List.class }, new Object[] { list });
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
SecurityContextHolderStrategy saved = SecurityContextHolder.getContextHolderStrategy();
SecurityContextHolder.setContextHolderStrategy(strategy);
advice.invoke(invocation);
verify(strategy).getContext();
SecurityContextHolder.setContextHolderStrategy(saved);
}
@PreFilter("filterObject == 'john'")
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {