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'
}
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).
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.