it-swarm.com.de

"Ungültige Bean-Definition" bei der Migration von Spring Boot 2.0.6 nach 2.1.0 mit EvaluationContextExtensionSupport und benutzerdefiniertem PermissionEvaluator

In Spring Boot 2.1.0 ist EvaluationContextExtensionSupport veraltet und https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/query/spi/EvaluationContextExtensionSupport.html sagt zu EvaluationContextExtension direkt implementieren

Obwohl es nur veraltet ist, schlägt es bei diesem Upgrade sofort mit diesem Stacktrace fehl:

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

Ich überschreibe diese Bean nicht explizit, also vermute ich, dass dies nur ein Nebeneffekt unseres aktuellen Codes ist. Wenn ich erlaube, dass Bean mit spring.main.allow-bean-definition-overriding=true gemäß https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1-Release-Notes#bean-overriding überschrieben wird, dann einfach Holen Sie sich eine andere Ausnahme.

Java.lang.IllegalStateException: Duplicate key org.springframework.data.spel.ExtensionA[email protected]10dfbbbb at Java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.Java:133) ~[na:1.8.0_162]

Ich möchte jedoch nicht einmal das Bean-Verhalten außer Kraft setzen. Das Ziel ist, dass ein benutzerdefiniertes Berechtigungsauswertungsprogramm wieder so funktioniert, wie es Spring beabsichtigt. 

So funktionierte es in der letzten Version:

In Spring Boot 2.0.6 hatten wir folgendes, um unsere benutzerdefinierte PermissionEvaluator-Klasse zum Laufen zu bringen:

Eine Klasse, die EvaluationContextExtensionSupport erweitert hat

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

Und dann eine Klasse, in der der Ausdruckshandler mit unserem Berechtigungsauswerter und mit einer @Bean mit einem EvaluationContextExtension erstellt wird.

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

Und zum Schluss haben wir das in einer sonst meist leeren Klasse:

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

Dies liegt daran, dass das benutzerdefinierte Berechtigungsauswertungsprogramm niemals auf alle Methoden angewendet wurde, wenn wir dies einfach in die MethodSecurityConfiguration-Klasse einfügen .. Der betreffende Server ist ein oauth2-Ressourcenserver, sodass wir in WebSecurityConfigurerAdapter nichts anderes konfigurieren. Wir implementieren auch unsere eigene UserDetails und erweitern die DefaultUserAuthenticationConverter, falls dies für die neue Lösung in irgendeiner Weise relevant ist.

Ich habe versucht, die Klasse EvaluationContextExtension direkt zu implementieren, wie in der Warnung zu veraltet. Es ist nur eine einfache Änderung, indem die Erweiterungsschnittstelle in implements EvaluationContextExtension..__ geändert wird. Ich habe auch versucht, das scheinbar neuere Paket org.springframework.data.spel.spi zu ändern.

Ich habe versucht, unser eigenes SecurityEvaluationContextExtension zu löschen und https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.html) zurückzugeben als Bean direkt, aber aus irgendeinem Grund ist das Datenpaket in Spring Boot 2.1.0 nicht verfügbar

Ich habe versucht, die Definition dieser Bohne ganz zu entfernen.

Alle diese Dinge führen beim Start zu verschiedenen Fehlern bei der 'ungültigen Bean-Definition'.

Weiß jemand, wo Sie einen Migrationsleitfaden oder eine andere Quelle finden, wie dies jetzt funktionieren soll?

Nur zur Information die eigentliche CustomPermissionEvaluator-Klasse:

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

Und ein Beispiel, wie es verwendet wird:

public interface ProjectRepository extends PagingAndSortingRepository<Project, UUID> {

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

Ich habe Ihren Code genommen und daraus eine Beispiel-App erstellt. Ich habe es hier gepostet:

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

Ihre @EnableGlobalMethodSecurity-Anmerkung befindet sich in WebSecurityConfigurerAdapter. Sie haben auch eine Klasse, die GlobalMethodSecurityConfiguration erweitert. Dies kann zu bestimmten Startproblemen beim Start führen, was möglicherweise das ist, was Sie sehen => Es werden zwei MethodSecurityExpressionHandlers erstellt sowie zwei EvaluationContextExtensions.

Ob dies genau der Fall ist oder nicht (ich vermute, dass es so ist), wenn ich Ihren @EnableGlobalMethodSecurity mit Ihrer benutzerdefinierten GlobalMethodSecurityConfiguration abgeglichen habe, haben die Dinge gut angefangen.

Es scheint jedoch, dass Ihre benutzerdefinierte EvaluationContextExtension dem Spring Security-Standard sehr ähnlich ist. Sie können die Klasse sowie die entsprechende Bean-Methode entfernen, wenn Sie können, da Spring Boot eine Klasse automatisch freigibt, wenn Sie spring-boot-starter-security und spring-security-data als Abhängigkeiten haben.

3
jzheaux

Sie überschreiben methodSecurityInterceptor bean. Es funktionierte zuvor, da das Überschreiben von Bohnen erlaubt war. 

Die Bean-Überschreibung wurde standardmäßig deaktiviert, um zu verhindern, dass eine Bean versehentlich überschrieben wird. Wenn Sie sich auf das Überschreiben verlassen, müssen Sie spring.main.allow-bean-definition-overriding auf true setzen.

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

2
Sukhpal Singh