diff --git a/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java index 6a70491d7c..8da167f306 100644 --- a/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java @@ -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 @@ -116,6 +117,10 @@ public abstract class AbstractSecurityExpressionHandler this.permissionEvaluator = permissionEvaluator; } + protected BeanResolver getBeanResolver() { + return this.beanResolver; + } + @Override public void setApplicationContext(ApplicationContext applicationContext) { this.beanResolver = new BeanFactoryResolver(applicationContext); diff --git a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionHandler.java index 39e171c4dd..6a59634714 100644 --- a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionHandler.java @@ -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 extends AopInfrastructureBean { @@ -41,4 +44,19 @@ public interface SecurityExpressionHandler 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, T invocation) { + return createEvaluationContext(authentication.get(), invocation); + } + } diff --git a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java index 155b317638..de27a7d063 100644 --- a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java +++ b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java @@ -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; 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) { + 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 getAuthoritySet() { if (this.roles == null) { - Collection userAuthorities = this.authentication.getAuthorities(); + Collection 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 { + + private Authentication value; + + private final Supplier delegate; + + private AuthenticationSupplier(Supplier 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; + } + + } + } diff --git a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java index 6254ee087d..1c76e2b47a 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java @@ -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 @@ -77,12 +79,26 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer()); } + @Override + public EvaluationContext createEvaluationContext(Supplier 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, + MethodInvocation invocation) { MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication); root.setThis(invocation.getThis()); root.setPermissionEvaluator(getPermissionEvaluator()); diff --git a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContext.java b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContext.java index 5ba0a376b6..b341356551 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContext.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContext.java @@ -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())); } diff --git a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRoot.java index a4d3f0a015..5e17069a53 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRoot.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRoot.java @@ -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) { + super(authentication); + } + @Override public void setFilterObject(Object filterObject) { this.filterObject = filterObject; diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java index 7d594e3705..3d605f0853 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java @@ -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); diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java index 737d7aa8e2..10794fde8c 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java @@ -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); } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java index b3d844cc75..cbf861a2bb 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java @@ -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); } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java index d9cfc29dee..148be0a3b5 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java @@ -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(); diff --git a/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java b/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java index ce5d2b015e..33c4a77fac 100644 --- a/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java +++ b/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java @@ -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 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() { diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java index 4a0e6e8ffa..5721cb8b44 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java +++ b/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java @@ -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,10 @@ package org.springframework.security.messaging.access.expression; +import java.util.function.Supplier; + +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.messaging.Message; import org.springframework.security.access.expression.AbstractSecurityExpressionHandler; import org.springframework.security.access.expression.SecurityExpressionHandler; @@ -31,15 +35,29 @@ import org.springframework.util.Assert; * * @param the type for the body of the Message * @author Rob Winch + * @author Evgeniy Cheban * @since 4.0 */ public class DefaultMessageSecurityExpressionHandler extends AbstractSecurityExpressionHandler> { private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + @Override + public EvaluationContext createEvaluationContext(Supplier authentication, Message message) { + MessageSecurityExpressionRoot root = createSecurityExpressionRoot(authentication, message); + StandardEvaluationContext ctx = new StandardEvaluationContext(root); + ctx.setBeanResolver(getBeanResolver()); + return ctx; + } + @Override protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, Message invocation) { + return createSecurityExpressionRoot(() -> authentication, invocation); + } + + private MessageSecurityExpressionRoot createSecurityExpressionRoot(Supplier authentication, + Message invocation) { MessageSecurityExpressionRoot root = new MessageSecurityExpressionRoot(authentication, invocation); root.setPermissionEvaluator(getPermissionEvaluator()); root.setTrustResolver(this.trustResolver); diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java index 710fbeb154..21f95eeabc 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java +++ b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageSecurityExpressionRoot.java @@ -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.messaging.access.expression; +import java.util.function.Supplier; + import org.springframework.messaging.Message; import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.core.Authentication; @@ -24,6 +26,7 @@ import org.springframework.security.core.Authentication; * The {@link SecurityExpressionRoot} used for {@link Message} expressions. * * @author Rob Winch + * @author Evgeniy Cheban * @since 4.0 */ public class MessageSecurityExpressionRoot extends SecurityExpressionRoot { @@ -31,6 +34,17 @@ public class MessageSecurityExpressionRoot extends SecurityExpressionRoot { public final Message message; public MessageSecurityExpressionRoot(Authentication authentication, Message message) { + this(() -> authentication, message); + } + + /** + * Creates an instance for the given {@link Supplier} of the {@link Authentication} + * and {@link Message}. + * @param authentication the {@link Supplier} of the {@link Authentication} to use + * @param message the {@link Message} to use + * @since 5.8 + */ + public MessageSecurityExpressionRoot(Supplier authentication, Message message) { super(authentication); this.message = message; } diff --git a/messaging/src/test/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandlerTests.java b/messaging/src/test/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandlerTests.java index 52d83897ef..9e62e6a15d 100644 --- a/messaging/src/test/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandlerTests.java +++ b/messaging/src/test/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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,9 @@ package org.springframework.security.messaging.access.expression; +import java.util.function.Supplier; + +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -24,10 +27,12 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; +import org.springframework.expression.TypedValue; import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.expression.ExpressionUtils; +import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationTrustResolver; @@ -38,6 +43,9 @@ import org.springframework.security.core.authority.AuthorityUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; @ExtendWith(MockitoExtension.class) public class DefaultMessageSecurityExpressionHandlerTests { @@ -104,4 +112,16 @@ public class DefaultMessageSecurityExpressionHandlerTests { assertThat(ExpressionUtils.evaluateAsBoolean(expression, context)).isTrue(); } + @Test + public void createEvaluationContextSupplierAuthentication() { + Supplier mockAuthenticationSupplier = mock(Supplier.class); + given(mockAuthenticationSupplier.get()).willReturn(this.authentication); + EvaluationContext context = this.handler.createEvaluationContext(mockAuthenticationSupplier, this.message); + verifyNoInteractions(mockAuthenticationSupplier); + assertThat(context.getRootObject()).extracting(TypedValue::getValue) + .asInstanceOf(InstanceOfAssertFactories.type(MessageSecurityExpressionRoot.class)) + .extracting(SecurityExpressionRoot::getAuthentication).isEqualTo(this.authentication); + verify(mockAuthenticationSupplier).get(); + } + } diff --git a/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java b/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java index 8533e214af..a75c0d2959 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java @@ -16,6 +16,10 @@ package org.springframework.security.web.access.expression; +import java.util.function.Supplier; + +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.security.access.expression.AbstractSecurityExpressionHandler; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.access.expression.SecurityExpressionOperations; @@ -39,9 +43,24 @@ public class DefaultHttpSecurityExpressionHandler extends AbstractSecurityExpres private String defaultRolePrefix = "ROLE_"; + @Override + public EvaluationContext createEvaluationContext(Supplier authentication, + RequestAuthorizationContext context) { + WebSecurityExpressionRoot root = createSecurityExpressionRoot(authentication, context); + StandardEvaluationContext ctx = new StandardEvaluationContext(root); + ctx.setBeanResolver(getBeanResolver()); + context.getVariables().forEach(ctx::setVariable); + return ctx; + } + @Override protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, RequestAuthorizationContext context) { + return createSecurityExpressionRoot(() -> authentication, context); + } + + private WebSecurityExpressionRoot createSecurityExpressionRoot(Supplier authentication, + RequestAuthorizationContext context) { WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, context.getRequest()); root.setRoleHierarchy(getRoleHierarchy()); root.setPermissionEvaluator(getPermissionEvaluator()); diff --git a/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java b/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java index f910c4bf55..8b9ed3c402 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java @@ -16,7 +16,6 @@ package org.springframework.security.web.access.expression; -import java.util.Map; import java.util.function.Supplier; import org.springframework.expression.EvaluationContext; @@ -72,10 +71,7 @@ public final class WebExpressionAuthorizationManager implements AuthorizationMan */ @Override public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext context) { - EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), context); - for (Map.Entry entry : context.getVariables().entrySet()) { - ctx.setVariable(entry.getKey(), entry.getValue()); - } + EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, context); boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx); return new ExpressionAuthorizationDecision(granted, this.expression); } diff --git a/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java b/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java index 497e9b844a..f2706a152a 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java @@ -16,8 +16,9 @@ package org.springframework.security.web.access.expression; -import jakarta.servlet.http.HttpServletRequest; +import java.util.function.Supplier; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.core.Authentication; import org.springframework.security.web.FilterInvocation; @@ -36,17 +37,17 @@ public class WebSecurityExpressionRoot extends SecurityExpressionRoot { public final HttpServletRequest request; public WebSecurityExpressionRoot(Authentication a, FilterInvocation fi) { - this(a, fi.getRequest()); + this(() -> a, fi.getRequest()); } /** - * Creates an instance for the given {@link Authentication} and - * {@link HttpServletRequest}. - * @param authentication the {@link Authentication} to use + * Creates an instance for the given {@link Supplier} of the {@link Authentication} + * and {@link HttpServletRequest}. + * @param authentication the {@link Supplier} of the {@link Authentication} to use * @param request the {@link HttpServletRequest} to use * @since 5.8 */ - public WebSecurityExpressionRoot(Authentication authentication, HttpServletRequest request) { + public WebSecurityExpressionRoot(Supplier authentication, HttpServletRequest request) { super(authentication); this.request = request; } diff --git a/web/src/test/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandlerTests.java b/web/src/test/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandlerTests.java index c784765bb0..5c58c7fe90 100644 --- a/web/src/test/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandlerTests.java +++ b/web/src/test/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandlerTests.java @@ -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,9 @@ package org.springframework.security.web.access.expression; +import java.util.function.Supplier; + +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -28,7 +31,9 @@ import org.springframework.context.support.StaticApplicationContext; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; +import org.springframework.expression.TypedValue; import org.springframework.security.access.SecurityConfig; +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; @@ -36,8 +41,10 @@ import org.springframework.security.web.access.intercept.RequestAuthorizationCon import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; @ExtendWith(MockitoExtension.class) public class DefaultHttpSecurityExpressionHandlerTests { @@ -91,4 +98,16 @@ public class DefaultHttpSecurityExpressionHandlerTests { verify(this.trustResolver).isAnonymous(this.authentication); } + @Test + public void createEvaluationContextSupplierAuthentication() { + Supplier mockAuthenticationSupplier = mock(Supplier.class); + given(mockAuthenticationSupplier.get()).willReturn(this.authentication); + EvaluationContext context = this.handler.createEvaluationContext(mockAuthenticationSupplier, this.context); + verifyNoInteractions(mockAuthenticationSupplier); + assertThat(context.getRootObject()).extracting(TypedValue::getValue) + .asInstanceOf(InstanceOfAssertFactories.type(WebSecurityExpressionRoot.class)) + .extracting(SecurityExpressionRoot::getAuthentication).isEqualTo(this.authentication); + verify(mockAuthenticationSupplier).get(); + } + }