1
0
mirror of synced 2026-08-05 17:57:15 +00:00

createEvaluationContext should defer lookup of Authentication

- Added createEvaluationContext method that accepts Supplier<Authentication>
- Refactored classes that use EvaluationContext to use lazy initialization of Authentication

Closes gh-9667
This commit is contained in:
Evgeniy Cheban
2022-05-07 21:28:54 +03:00
committed by Josh Cummings
parent 2be141f800
commit 5540bbcf0b
18 changed files with 236 additions and 44 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -35,6 +35,7 @@ import org.springframework.util.Assert;
* objects.
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @since 3.1
*/
public abstract class AbstractSecurityExpressionHandler<T>
@@ -116,6 +117,10 @@ public abstract class AbstractSecurityExpressionHandler<T>
this.permissionEvaluator = permissionEvaluator;
}
protected BeanResolver getBeanResolver() {
return this.beanResolver;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.beanResolver = new BeanFactoryResolver(applicationContext);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -16,6 +16,8 @@
package org.springframework.security.access.expression;
import java.util.function.Supplier;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
@@ -26,6 +28,7 @@ import org.springframework.security.core.Authentication;
* expressions from the implementation of the underlying expression objects
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @since 3.1
*/
public interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
@@ -41,4 +44,19 @@ public interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
*/
EvaluationContext createEvaluationContext(Authentication authentication, T invocation);
/**
* Provides an evaluation context in which to evaluate security expressions for the
* invocation type. You can override this method in order to provide a custom
* implementation that uses lazy initialization of the {@link Authentication} object.
* By default, this method uses eager initialization of the {@link Authentication}
* object.
* @param authentication the {@link Supplier} of the {@link Authentication} to use
* @param invocation the {@link T} to use
* @return the {@link EvaluationContext} to use
* @since 5.8
*/
default EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, T invocation) {
return createEvaluationContext(authentication.get(), invocation);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.access.expression;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
@@ -26,16 +27,18 @@ import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.util.Assert;
/**
* Base root object for use in Spring Security expression evaluations.
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @since 3.0
*/
public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
protected final Authentication authentication;
private final Supplier<Authentication> authentication;
private AuthenticationTrustResolver trustResolver;
@@ -72,10 +75,18 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
* @param authentication the {@link Authentication} to use. Cannot be null.
*/
public SecurityExpressionRoot(Authentication authentication) {
if (authentication == null) {
throw new IllegalArgumentException("Authentication object cannot be null");
}
this.authentication = authentication;
this(() -> authentication);
}
/**
* Creates a new instance that uses lazy initialization of the {@link Authentication}
* object.
* @param authentication the {@link Supplier} of the {@link Authentication} to use.
* Cannot be null.
* @since 5.8
*/
public SecurityExpressionRoot(Supplier<Authentication> authentication) {
this.authentication = new AuthenticationSupplier(authentication);
}
@Override
@@ -111,7 +122,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
@Override
public final Authentication getAuthentication() {
return this.authentication;
return this.authentication.get();
}
@Override
@@ -126,7 +137,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
@Override
public final boolean isAnonymous() {
return this.trustResolver.isAnonymous(this.authentication);
return this.trustResolver.isAnonymous(getAuthentication());
}
@Override
@@ -136,13 +147,13 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
@Override
public final boolean isRememberMe() {
return this.trustResolver.isRememberMe(this.authentication);
return this.trustResolver.isRememberMe(getAuthentication());
}
@Override
public final boolean isFullyAuthenticated() {
return !this.trustResolver.isAnonymous(this.authentication)
&& !this.trustResolver.isRememberMe(this.authentication);
Authentication authentication = getAuthentication();
return !this.trustResolver.isAnonymous(authentication) && !this.trustResolver.isRememberMe(authentication);
}
/**
@@ -151,7 +162,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
* @return
*/
public Object getPrincipal() {
return this.authentication.getPrincipal();
return getAuthentication().getPrincipal();
}
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
@@ -181,7 +192,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
private Set<String> getAuthoritySet() {
if (this.roles == null) {
Collection<? extends GrantedAuthority> userAuthorities = this.authentication.getAuthorities();
Collection<? extends GrantedAuthority> userAuthorities = getAuthentication().getAuthorities();
if (this.roleHierarchy != null) {
userAuthorities = this.roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
}
@@ -192,12 +203,12 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
@Override
public boolean hasPermission(Object target, Object permission) {
return this.permissionEvaluator.hasPermission(this.authentication, target, permission);
return this.permissionEvaluator.hasPermission(getAuthentication(), target, permission);
}
@Override
public boolean hasPermission(Object targetId, String targetType, Object permission) {
return this.permissionEvaluator.hasPermission(this.authentication, (Serializable) targetId, targetType,
return this.permissionEvaluator.hasPermission(getAuthentication(), (Serializable) targetId, targetType,
permission);
}
@@ -225,4 +236,27 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
return defaultRolePrefix + role;
}
private static final class AuthenticationSupplier implements Supplier<Authentication> {
private Authentication value;
private final Supplier<Authentication> delegate;
private AuthenticationSupplier(Supplier<Authentication> delegate) {
Assert.notNull(delegate, "delegate cannot be null");
this.delegate = delegate;
}
@Override
public Authentication get() {
if (this.value == null) {
Authentication authentication = this.delegate.get();
Assert.notNull(authentication, "Authentication object cannot be null");
this.value = authentication;
}
return this.value;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.aopalliance.intercept.MethodInvocation;
@@ -50,6 +51,7 @@ import org.springframework.util.Assert;
* support.
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @since 3.0
*/
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
@@ -77,12 +79,26 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
}
@Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
MethodSecurityExpressionOperations root = createSecurityExpressionRoot(authentication, mi);
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(root, mi,
getParameterNameDiscoverer());
ctx.setBeanResolver(getBeanResolver());
return ctx;
}
/**
* Creates the root object for expression evaluation.
*/
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
MethodInvocation invocation) {
return createSecurityExpressionRoot(() -> authentication, invocation);
}
private MethodSecurityExpressionOperations createSecurityExpressionRoot(Supplier<Authentication> authentication,
MethodInvocation invocation) {
MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -34,6 +34,7 @@ import org.springframework.security.core.parameters.DefaultSecurityParameterName
*
* @author Luke Taylor
* @author Daniel Bustamante
* @author Evgeniy Cheban
* @since 3.0
*/
class MethodSecurityEvaluationContext extends MethodBasedEvaluationContext {
@@ -52,6 +53,11 @@ class MethodSecurityEvaluationContext extends MethodBasedEvaluationContext {
super(mi.getThis(), getSpecificMethod(mi), mi.getArguments(), parameterNameDiscoverer);
}
MethodSecurityEvaluationContext(MethodSecurityExpressionOperations root, MethodInvocation mi,
ParameterNameDiscoverer parameterNameDiscoverer) {
super(root, getSpecificMethod(mi), mi.getArguments(), parameterNameDiscoverer);
}
private static Method getSpecificMethod(MethodInvocation mi) {
return AopUtils.getMostSpecificMethod(mi.getMethod(), AopProxyUtils.ultimateTargetClass(mi.getThis()));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -16,6 +16,8 @@
package org.springframework.security.access.expression.method;
import java.util.function.Supplier;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.core.Authentication;
@@ -23,6 +25,7 @@ import org.springframework.security.core.Authentication;
* Extended expression root object which contains extra method-specific functionality.
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @since 3.0
*/
class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
@@ -37,6 +40,10 @@ class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements Met
super(a);
}
MethodSecurityExpressionRoot(Supplier<Authentication> authentication) {
super(authentication);
}
@Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -72,7 +72,7 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return null;
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication,
mi.getMethodInvocation());
this.expressionHandler.setReturnObject(mi.getResult(), ctx);
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -128,7 +128,7 @@ public final class PostFilterAuthorizationMethodInterceptor
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return returnedObject;
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(AUTHENTICATION_SUPPLIER.get(), mi);
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(AUTHENTICATION_SUPPLIER, mi);
return this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -72,7 +72,7 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return null;
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), mi);
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi);
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
return new ExpressionAttributeAuthorizationDecision(granted, attribute);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 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.
@@ -126,7 +126,7 @@ public final class PreFilterAuthorizationMethodInterceptor
if (attribute == PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
return mi.proceed();
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(AUTHENTICATION_SUPPLIER.get(), mi);
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(AUTHENTICATION_SUPPLIER, mi);
Object filterTarget = findFilterTarget(attribute.filterTarget, ctx, mi);
this.expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
return mi.proceed();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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,10 +19,12 @@ package org.springframework.security.access.expression.method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.aopalliance.intercept.MethodInvocation;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -32,6 +34,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -43,6 +47,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@ExtendWith(MockitoExtension.class)
public class DefaultMethodSecurityExpressionHandlerTests {
@@ -167,6 +172,20 @@ public class DefaultMethodSecurityExpressionHandlerTests {
verify(upstream).close();
}
@Test
public void createEvaluationContextSupplierAuthentication() {
setupMocks();
Supplier<Authentication> mockAuthenticationSupplier = mock(Supplier.class);
given(mockAuthenticationSupplier.get()).willReturn(this.authentication);
EvaluationContext context = this.handler.createEvaluationContext(mockAuthenticationSupplier,
this.methodInvocation);
verifyNoInteractions(mockAuthenticationSupplier);
assertThat(context.getRootObject()).extracting(TypedValue::getValue)
.asInstanceOf(InstanceOfAssertFactories.type(MethodSecurityExpressionRoot.class))
.extracting(SecurityExpressionRoot::getAuthentication).isEqualTo(this.authentication);
verify(mockAuthenticationSupplier).get();
}
static class Foo {
void bar() {