web-dev-qa-db-fra.com

Comment ajouter une sécurité basée sur une méthode à un projet Spring Boot?

Je souhaite ajouter une sécurité basée sur les méthodes à un projet Spring Boot.

Il me semblait que tout ce dont j'avais besoin était d'ajouter des haricots PermissionEvaluator et MethodSecurityExpressionHandler, d'annoter ma WebSecurityConfigurerAdapter avec @EnableGlobalMethodSecurity(prePostEnabled = true) et la méthode avec @PreAuthorize("isAuthenticated() and hasPermission(#param, 'somePermissionName')").

Mais après avoir ajouté un haricot PermissionEvaluator

@Bean
public PermissionEvaluator permissionEvaluator() {
    HelloPermissionEvaluator bean = new HelloPermissionEvaluator();
    return bean;
}

Je reçois une IllegalArgumentException: "Un ServletContext est requis pour configurer la gestion des servlets par défaut":

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultServletHandlerMapping' defined in class path resource [org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.web.servlet.HandlerMapping org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.defaultServletHandlerMapping()] threw exception; nested exception is Java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.Java:597)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.Java:1094)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.Java:989)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.Java:504)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.Java:475)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.Java:304)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.Java:228)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.Java:300)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.Java:195)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.Java:703)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.Java:760)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.Java:482)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.Java:120)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.Java:648)
    at org.springframework.boot.SpringApplication.run(SpringApplication.Java:311)
    at org.springframework.boot.SpringApplication.run(SpringApplication.Java:909)
    at org.springframework.boot.SpringApplication.run(SpringApplication.Java:898)
    at com.domain.simple.Application.main(Application.Java:14)
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.web.servlet.HandlerMapping org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.defaultServletHandlerMapping()] threw exception; nested exception is Java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.Java:188)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.Java:586)
    ... 17 more
Caused by: Java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
    at org.springframework.util.Assert.notNull(Assert.Java:112)
    at org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer.<init>(DefaultServletHandlerConfigurer.Java:54)
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.defaultServletHandlerMapping(WebMvcConfigurationSupport.Java:346)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$d7f296e3.CGLIB$defaultServletHandlerMapping$26(<generated>)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$d7f296e3$$FastClassBySpringCGLIB$$48c20692.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.Java:228)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.Java:312)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$d7f296e3.defaultServletHandlerMapping(<generated>)
    at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:62)
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
    at Java.lang.reflect.Method.invoke(Method.Java:483)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.Java:166)
    ... 18 more

Tout ce que j'ai pu trouver sur le Web est lié aux tests jUnit. Pourquoi cette exception est-elle levée? Qu'est-ce que je rate? Dois-je ajouter un bean ServletContext, et si oui, comment?

Mes exigences sont Gradle, Spring Boot et Java config (au lieu de XML config). La source minimale et complète suit:


Application.Java

package com.domain.simple;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@EnableAutoConfiguration
@Configuration
@ComponentScan
public class Application {

    public static void main(String[] args) throws Throwable {
        SpringApplication.run(Application.class, args);
    }

}

HelloController.Java

package com.domain.simple;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloController {

    Logger log = LoggerFactory.getLogger(HelloController.class);

    // @PreAuthorize("isAuthenticated() and hasPermission(#param, 'somePermissionName')")
    @RequestMapping(value = "/hello/{param}")
    @ResponseBody
    public String hello(@PathVariable("param") String param) {
        log.info("hello(" + param + ") called");
        return "Hello " + param;
    }
}

HelloPermissionEvaluator.Java

package com.domain.simple;

import Java.io.Serializable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;

public class HelloPermissionEvaluator implements PermissionEvaluator {

    Logger log = LoggerFactory.getLogger(HelloPermissionEvaluator.class);

    @Override
    public boolean hasPermission(Authentication authentication,
            Object targetDomainObject, Object permission) {
        log.info("hasPermission(Authentication, Object, Object) called");
        return true;
    }

    @Override
    public boolean hasPermission(Authentication authentication,
            Serializable targetId, String targetType, Object permission) {
        log.error("hasPermission(Authentication, Serializable, String, Object) called");
        throw new RuntimeException("ID based permission evaluation currently not supported.");
    }
}

WebSecurityConfig.Java

package com.domain.simple;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@ComponentScan
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder authManagerBuilder)
            throws Exception {
        authManagerBuilder.inMemoryAuthentication().withUser("user")
                .password("password").roles("USER");
    }

//    @Bean
//    public MethodSecurityExpressionHandler expressionHandler() {
//        DefaultMethodSecurityExpressionHandler bean = new DefaultMethodSecurityExpressionHandler();
//        bean.setPermissionEvaluator(permissionEvaluator());
//        return bean;
//    }

    // this causes an IllegalArgumentException ("A ServletContext is required to configure default servlet handling")
    @Bean
    public PermissionEvaluator permissionEvaluator() {
        HelloPermissionEvaluator bean = new HelloPermissionEvaluator();
        return bean;
    }

}

build.gradle

buildscript {
    repositories {
        maven { url "http://repo.spring.io/libs-snapshot" }
        mavenLocal()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.2.RELEASE")
    }
}

apply plugin: 'Eclipse'
apply plugin: 'Java'
apply plugin: 'spring-boot'

jar {
    baseName = 'simple'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
    maven { url "http://repo.spring.io/libs-snapshot" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-security")
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.12'
}
18
Yurim

Essayez de placer PermissionEvaluator dans une classe @Configuration séparée. Vous semblez le forcer à être instancié avant que ServletContext soit prêt (les filtres de sécurité Spring doivent être créés très tôt pour que cela puisse se produire).

31
Dave Syer

Couru dans un problème similaire. Si vous souhaitez configurer un évaluateur d’autorisations personnalisé dans le gestionnaire d’expression, vous pouvez procéder de la sorte

public class SecurityPermissionEvaluator implements PermissionEvaluator 
    {
    //A is a spring managed bean on which permission evaluator depends
    private A a;

    @Autowired
    public  SecurityPermissionEvaluator(A a){
        this.a = a;
    }
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {

        return false;
    }
}

then

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends GlobalMethodSecurityConfiguration {

    @Autowired private A a;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler =
                new DefaultMethodSecurityExpressionHandler();
        PermissionEvaluator permissionEvaluator = new SecurityPermissionEvaluator(a);
        expressionHandler.setPermissionEvaluator(permissionEvaluator);
        return expressionHandler;
    }

}

si vous voulez utiliser explicitement l’évaluateur d’autorisation, faites comme indiqué par Dave, c’est-à-dire, définissez le bean évaluateur d’autorisation dans son fichier de configuration.

0
Anil Punia