In Spring Boot 2.1.0 EvaluationContextExtensionSupport
is deprecated and https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/query/spi/EvaluationContextExtensionSupport.html says to Implement EvaluationContextExtension directly
Even though it is only deprecated, it instantly starting failing on this upgrade with this stacktrace:
Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'methodSecurityInterceptor' defined in class path resource [org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.class]: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; factoryMethodName=methodSecurityInterceptor; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.class]] for bean 'methodSecurityInterceptor': There is already [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=methodSecurityConfiguration; factoryMethodName=methodSecurityInterceptor; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [ournamespace/configuration/MethodSecurityConfiguration.class]] bound.
at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:894)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:274)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:141)
...and so on
I don't explicitly override this bean, so I'm guessing this is just a side effect of what we're doing in our current code. If I do allow bean overriding with spring.main.allow-bean-definition-overriding=true
as per https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1-Release-Notes#bean-overriding then I simply get another exception.
java.lang.IllegalStateException: Duplicate key org.springframework.data.spel.ExtensionAwareEvaluationContextProvider$EvaluationContextExtensionAdapter@10dfbbbb at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133) ~[na:1.8.0_162]
However, I don't even want to override any bean behavior, the goal is to get a custom permission evaluator working again the way Spring intends it to work.
This is how it did work in the last version:
In Spring Boot 2.0.6 we had the following to get our custom PermissionEvaluator class to work:
A class that extended EvaluationContextExtensionSupport
import org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {
@Override
public String getExtensionId() {
return "security";
}
@Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {
};
}
}
And then a class where the expression handler is created with our permission evaluator, and with a @Bean with a EvaluationContextExtension
import ournamespace.security.CustomPermissionEvaluator;
import ournamespace.security.SecurityEvaluationContextExtension;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.query.spi.EvaluationContextExtension;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
@Configuration
@RequiredArgsConstructor
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
private final CustomPermissionEvaluator permissionEvaluator;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
@Bean
EvaluationContextExtension securityExtension() {
return new SecurityEvaluationContextExtension();
}
}
And finally we have this in an otherwise mostly empty class:
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
...
}
This is because the custom permission evaluator never did apply to all methods if we just put this in the MethodSecurityConfiguration
class.
The server in question is an oauth2 resource server so we don't configure anything else in the WebSecurityConfigurerAdapter
. We also implement our own UserDetails
and we extend the DefaultUserAuthenticationConverter
, if this is in any way relevant for the new solution.
I have tried implementing EvaluationContextExtension
class directly, as stated in the deprecation warning. It's just a simple modification by changing the extends interface to implements EvaluationContextExtension
.
I have also tried changing to the seemingly newer package org.springframework.data.spel.spi
I've tried deleting our own SecurityEvaluationContextExtension
and returning https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.html as a bean directly but for some reason that data package isn't available in Spring Boot 2.1.0
I've tried just removing the definition of that bean altogether.
All these things result in various 'invalid bean definition' errors on startup.
Does anyone know where to find a migration guide or any other resource on how this is supposed to work now?
Just for reference sake, the actual CustomPermissionEvaluator
class:
import ournamespace.configuration.Constants;
import ournamespace.exception.InternalException;
import ournamespace.model.Account;
import ournamespace.model.Member;
import ournamespace.model.Project;
import ournamespace.repository.MemberRepository;
import ournamespace.service.ServiceUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import static ournamespace.model.MemberStatus.JOINED;
import static ournamespace.model.ProjectRole.*;
@RequiredArgsConstructor
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
private final MemberRepository memberRepository;
@Override
public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) {
if (targetDomainObject == null)
return false;
if (!(permission instanceof String))
return false;
if (auth == null)
return false;
Account account = ServiceUtil.getAccount(auth);
if (targetDomainObject instanceof Project)
return hasPermissionOnProject(account, (Project) targetDomainObject, (String) permission);
//and so on
}
}
And an example on how it's used:
public interface ProjectRepository extends PagingAndSortingRepository<Project, UUID> {
@Override
@PreAuthorize("hasPermission(#project, " + Constants.WRITE + ")")
<S extends Project> S save(@Param("project") S project);
}
I took your code and created a sample app from it. I posted it here:
https://github.com/jzheaux/stackoverflow-53410526
Your @EnableGlobalMethodSecurity
annotation is on WebSecurityConfigurerAdapter
. You also have a class that extends GlobalMethodSecurityConfiguration
. This can cause some ordering issues at startup at times, which may be what you are seeing => two MethodSecurityExpressionHandler
s get created as well as two EvaluationContextExtension
s.
Whether this is precisely the case or not (I'm guessing that it is), when I matched your @EnableGlobalMethodSecurity
with your custom GlobalMethodSecurityConfiguration
, things started up fine.
Also, though, it seems that your custom EvaluationContextExtension
is very similar to the Spring Security default. You might consider removing that class as well as the corresponding bean method, if you can, since Spring Boot exposes one automatically when you have spring-boot-starter-security
and spring-security-data
as dependencies.
You're overriding methodSecurityInterceptor
bean. It was working previously as bean overriding was allowed.
Bean overriding has been disabled by default to prevent a bean being accidentally overridden. If you are relying on overriding, you will need to set spring.main.allow-bean-definition-overriding to true.
Spring-Boot-2.1-Release-Notes#bean-overriding
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With