使用EvaluationContextExtensionSupport和自定义PermissionEvaluator

时间:2018-11-21 10:52:33

标签: java spring spring-boot spring-security

在Spring Boot 2.1.0中,EvaluationContextExtensionSupport已过时,https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/query/spi/EvaluationContextExtensionSupport.html表示直接实施EvaluationContextExtension

即使只弃用了它,但使用此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

我没有显式重写此bean,所以我猜测这只是我们在当前代码中所做的副作用。如果我确实允许按照https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1-Release-Notes#bean-overridingspring.main.allow-bean-definition-overriding=true覆盖bean,那么我只会得到另一个异常。

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]

但是,我什至不想覆盖任何bean行为,目的是让自定义权限评估程序按照Spring预期的方式工作。

这是上一版本的工作方式:

在Spring Boot 2.0.6中,我们可以执行以下操作以使自定义PermissionEvaluator类起作用:

扩展了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) {
        };
    }
}

然后是一个类,其中使用我们的权限评估程序以及带有EvaluationContextExtension的@Bean创建表达式处理程序

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

最后,我们将其放在否则为空的类中:

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
   ...
}

这是因为如果仅将自定义权限评估器放在MethodSecurityConfiguration类中,则它永远不会应用于所有方法。 该服务器是oauth2资源服务器,因此我们没有在WebSecurityConfigurerAdapter中配置其他任何服务器。我们还实现了自己的UserDetails,并扩展了DefaultUserAuthenticationConverter(如果这与新解决方案有任何关系)。

如过时警告中所述,我尝试直接实现EvaluationContextExtension类。通过将扩展接口更改为implements EvaluationContextExtension,这只是一个简单的修改。 我还尝试过改用看似较新的软件包org.springframework.data.spel.spi

我尝试删除自己的SecurityEvaluationContextExtension并直接将https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.html作为bean返回,但是由于某些原因,Spring Boot 2.1.0中没有数据包

我尝试过完全删除该bean的定义。

所有这些都会在启动时导致各种“无效的bean定义”错误。

有人知道在哪里可以找到迁移指南或其他任何有关该如何工作的资源吗?

仅供参考,实际的CustomPermissionEvaluator类:

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
    }
}

以及如何使用它的示例:

public interface ProjectRepository extends PagingAndSortingRepository<Project, UUID> {

    @Override
    @PreAuthorize("hasPermission(#project, " + Constants.WRITE + ")")
    <S extends Project> S save(@Param("project") S project);
}

2 个答案:

答案 0 :(得分:3)

我获取了您的代码,并从中创建了一个示例应用程序。我在这里贴了:

https://github.com/jzheaux/stackoverflow-53410526

您的@EnableGlobalMethodSecurity注释位于WebSecurityConfigurerAdapter上。您还拥有一个扩展GlobalMethodSecurityConfiguration的类。这可能会在启动时引起一些排序问题,这可能是您看到的=>创建了两个MethodSecurityExpressionHandler和两个EvaluationContextExtension

这是否是事实(我猜是这样),当我将您的@EnableGlobalMethodSecurity与自定义GlobalMethodSecurityConfiguration相匹配时,一切就开始正常了。

不过,看来,您的自定义EvaluationContextExtension与Spring Security的默认值非常相似。如果可以的话,您可以考虑删除该类以及相应的bean方法,因为当您将spring-boot-starter-securityspring-security-data作为依赖项时,Spring Boot会自动显示一个类。

答案 1 :(得分:2)

您正在覆盖methodSecurityInterceptor bean。由于允许使用Bean覆盖,因此之前它一直在工作。

  

默认情况下禁用Bean覆盖,以防止意外覆盖Bean。如果您要依赖覆盖,则需要将spring.main.allow-bean-definition-overriding设置为true。

Spring-Boot-2.1-Release-Notes#bean-overriding