Skip to content

createEvaluationContext should defer lookup of Authentication #11187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -35,6 +35,7 @@
* objects.
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @since 3.1
*/
public abstract class AbstractSecurityExpressionHandler<T>
Expand Down Expand Up @@ -116,6 +117,10 @@ public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}

protected BeanResolver getBeanResolver() {
return this.beanResolver;
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.beanResolver = new BeanFactoryResolver(applicationContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -26,6 +28,7 @@
* expressions from the implementation of the underlying expression objects
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @since 3.1
*/
public interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
Expand All @@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -19,23 +19,26 @@
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;
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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -111,7 +122,7 @@ private boolean hasAnyAuthorityName(String prefix, String... roles) {

@Override
public final Authentication getAuthentication() {
return this.authentication;
return this.authentication.get();
}

@Override
Expand All @@ -126,7 +137,7 @@ public final boolean denyAll() {

@Override
public final boolean isAnonymous() {
return this.trustResolver.isAnonymous(this.authentication);
return this.trustResolver.isAnonymous(getAuthentication());
}

@Override
Expand All @@ -136,13 +147,13 @@ public final boolean isAuthenticated() {

@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);
}

/**
Expand All @@ -151,7 +162,7 @@ public final boolean isFullyAuthenticated() {
* @return
*/
public Object getPrincipal() {
return this.authentication.getPrincipal();
return getAuthentication().getPrincipal();
}

public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
Expand Down Expand Up @@ -181,7 +192,7 @@ public void setDefaultRolePrefix(String defaultRolePrefix) {

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);
}
Expand All @@ -192,12 +203,12 @@ private Set<String> getAuthoritySet() {

@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);
}

Expand Down Expand Up @@ -225,4 +236,27 @@ private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String
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;
}

}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -23,6 +23,7 @@
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;
Expand Down Expand Up @@ -50,6 +51,7 @@
* support.
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @since 3.0
*/
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
Expand Down Expand Up @@ -77,12 +79,26 @@ public StandardEvaluationContext createEvaluationContextInternal(Authentication
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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -34,6 +34,7 @@
*
* @author Luke Taylor
* @author Daniel Bustamante
* @author Evgeniy Cheban
* @since 3.0
*/
class MethodSecurityEvaluationContext extends MethodBasedEvaluationContext {
Expand All @@ -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()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -16,13 +16,16 @@

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;

/**
* 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 {
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -72,7 +72,7 @@ public AuthorizationDecision check(Supplier<Authentication> authentication, Meth
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -128,7 +128,7 @@ public Object invoke(MethodInvocation mi) throws Throwable {
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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -72,7 +72,7 @@ public AuthorizationDecision check(Supplier<Authentication> authentication, Meth
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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -126,7 +126,7 @@ public Object invoke(MethodInvocation mi) throws Throwable {
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();
Expand Down
Loading