Spring 使用 AclPermissionEvaluator 启动导致 IllegalStateException:未设置 ServletContext

Spring Boot with AclPermissionEvaluator resulting in IllegalStateException: No ServletContext set

专家您好,

我目前正在学习 Spring Boot,我想将它与 Spring 安全 ACL 一起使用。 按照 documentation of Spring Security and a tutorial on Baeldung.com, I think I got an understanding about what is needed. I also looked into the DMS example of Spring. I stumbled across another example 搜索解决方案。

基于这些信息,我构建了我的应用程序。作为参考,您可以在 GitHub.

上找到当前应用程序

当前一期

当我启动应用程序时,我收到 java.lang.IllegalStateException: No ServletContext set 抛出。据我了解,这是因为在 Spring Boot 的自动配置 magic 期间,我的 @Configuration 注释 classes 被初始化在 ServletContext 初始化之前。

Link 到 pastebin 上的完整堆栈跟踪。

到目前为止我做了什么

根据我的研究(主要是在 Whosebug 上)和我目前对该问题的理解,将负责的 Bean 放入自己的 @Configuration 注释 class 应该会有所帮助。不幸的是,我现在迷路了。 相关问题让我产生了这种想法(参见 , or this one。)

我的环境

相关项目文件

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>de.moritzrupp</groupId>
<artifactId>traderdiary</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Trader Diary</name>
<description>Trader Diary is an easy to use web application to create a journal 
    about your trades with great reporting on top of it.</description>

<licenses>
    <license>
        <name>GNU General Public License (GPL) v3.0</name>
        <url>https://www.gnu.org/licenses/gpl-3.0.txt</url>
    </license>
</licenses>

<developers>
    <developer>
        <id>moritzrupp</id>
        <name>Moritz Rupp</name>
        <email>moritz.rupp@gmail.com</email>
        <url>https://www.moritzrupp.de</url>
        <timezone>DE</timezone>
    </developer>
</developers>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-rest-hal-browser</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-hateoas</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-acl</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
    </dependency>
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-core</artifactId>
        <version>2.6.11</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.restdocs</groupId>
        <artifactId>spring-restdocs-mockmvc</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

application.properties

# --------------------------------------------
# Datasource Properties
# --------------------------------------------
spring.h2.console.enabled=true
spring.h2.console.path=/h2

spring.datasource.url=jdbc:h2:mem:trader-diary-h2-db
spring.datasource.platform=h2
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect

TraderDiaryApplication.java

@SpringBootApplication
public class TraderDiaryApplication {

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

DataSourceConfig.java

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource traderDiaryDataSource(DataSourceProperties 
        dataSourceProperties) {

        return dataSourceProperties.initializeDataSourceBuilder().build();
    }
}

WebSecurityConfig.java

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("traderDiaryDataSource")
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // I think it is not relevant for the issue, see GitHub repo
        ...
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        // I think it is not relevant for the issue, see GitHub repo
        ...
    }
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider(...) {

        // I think it is not relevant for the issue, see GitHub repo
        ...
    }

    @Bean
    public MethodSecurityExpressionHandler aclExpressionHandler() {

        DefaultMethodSecurityExpressionHandler expressionHandler = 
            new DefaultMethodSecurityExpressionHandler();

        AclPermissionCacheOptimizer permissionCacheOptimizer = 
            new AclPermissionCacheOptimizer(aclService());

        expressionHandler.setPermissionEvaluator(permissionEvaluator());
        expressionHandler
            .setPermissionCacheOptimizer(permissionCacheOptimizer);
        return expressionHandler;
    }

    @Bean
    public PermissionEvaluator permissionEvaluator() {
        return new AclPermissionEvaluator(aclService());
    }

    @Bean
    public JdbcMutableAclService aclService() {
        return new JdbcMutableAclService(dataSource, lookupStrategy(), 
            aclCache());
    }

    @Bean
    public LookupStrategy lookupStrategy() {
        return new BasicLookupStrategy(dataSource, aclCache(), 
            aclAuthorizationStrategy(), new ConsoleAuditLogger());
    }

    @Bean
    public EhCacheBasedAclCache aclCache() {
        return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), 
            permissionGrantingStrategy(), aclAuthorizationStrategy());
    }

    @Bean
    public EhCacheFactoryBean aclEhCacheFactoryBean() {
        EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
        ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
        ehCacheFactoryBean.setCacheName("aclCache");
        return ehCacheFactoryBean;
    }

    @Bean
    public EhCacheManagerFactoryBean aclCacheManager() {
        return new EhCacheManagerFactoryBean();
    }

    @Bean
    public PermissionGrantingStrategy permissionGrantingStrategy() {
        return new DefaultPermissionGrantingStrategy(new 
            ConsoleAuditLogger());
    }

    @Bean
    public AclAuthorizationStrategy aclAuthorizationStrategy() {
        return new AclAuthorizationStrategyImpl(
            new SimpleGrantedAuthority("ROLE_ADMIN"));
    }

    /* This is due to an earlier issue: DataSource required */
    @Configuration
    protected static class AclMethodSecurityConfig extends 
        GlobalMethodSecurityConfiguration {

        @Autowired
        @Qualifier("daoAuthenticationProvider")
        private AuthenticationProvider authenticationProvider;

        @Autowired
        @Qualifier("aclExpressionHandler")
        private MethodSecurityExpressionHandler aclExpressionHandler;



        @Autowired
        public void configureAuthManager(AuthenticationManagerBuilder 
            authenticationManagerBuilder) {

            authenticationManagerBuilder
                .authenticationProvider(authenticationProvider);
        }

        @Override
        protected MethodSecurityExpressionHandler 
            createExpressionHandler() {
                return aclExpressionHandler;
        }
    }
}

非常感谢所有的输入!如果需要任何进一步的信息,我很乐意提供。

谢谢和最诚挚的问候 莫里茨

大家好,

我自己设法解决了这个问题。我对 commenting/uncommenting 所有 MethodSecurity 相关的东西进行了一步一步的处理。

我将其归结为 DefaultMethodSecurityExpressionHandler 的创建。这导致 IllegalStateException: No ServletContext set.

然后,我创建了一个新的 Class MethodSecurityConfig.java 并将所有相关代码放在那里。现在应用程序又开始了,我可以继续开发了。

@Rakesh:感谢您的参与!

MethodSecurityConfig.java

@Configuration
public class MethodSecurityConfig {

    private DataSource dataSource;

    @Autowired
    @Qualifier("traderDiaryDataSource")
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public MethodSecurityExpressionHandler aclExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler =
            new DefaultMethodSecurityExpressionHandler();

        AclPermissionCacheOptimizer permissionCacheOptimizer =
            new AclPermissionCacheOptimizer(aclService());

            expressionHandler.setPermissionEvaluator(permissionEvaluator());
            expressionHandler
                .setPermissionCacheOptimizer(permissionCacheOptimizer);
        return expressionHandler;
    }

    @Bean
    public PermissionEvaluator permissionEvaluator() {
        return new AclPermissionEvaluator(aclService());
    }

    @Bean
    public JdbcMutableAclService aclService() {
        return new JdbcMutableAclService(dataSource,
            lookupStrategy(), aclCache());
    }

    @Bean
    public LookupStrategy lookupStrategy() {
        return new BasicLookupStrategy(dataSource, aclCache(),
            aclAuthorizationStrategy(), new ConsoleAuditLogger());
    }

    @Bean
    public EhCacheBasedAclCache aclCache() {
        return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), 
            permissionGrantingStrategy(), aclAuthorizationStrategy());
    }

    @Bean
    public EhCacheFactoryBean aclEhCacheFactoryBean() {
        EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
        ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
        ehCacheFactoryBean.setCacheName("aclCache");
        return ehCacheFactoryBean;
    }

    @Bean
    public EhCacheManagerFactoryBean aclCacheManager() {
        return new EhCacheManagerFactoryBean();
    }

    @Bean
    public PermissionGrantingStrategy permissionGrantingStrategy() {
        return new DefaultPermissionGrantingStrategy(
            new ConsoleAuditLogger());
    }

    @Bean
    public AclAuthorizationStrategy aclAuthorizationStrategy() {
        return new AclAuthorizationStrategyImpl(
            new SimpleGrantedAuthority("ROLE_ADMIN"));
    }
}

WebSecurityConfig.java

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    ...

    @Configuration
    protected static class AclMethodSecurityConfig
        extends GlobalMethodSecurityConfiguration {

        private MethodSecurityExpressionHandler aclExpressionHandler;

        @Autowired
        @Qualifier("aclExpressionHandler")
        public void setAclExpressionHandler(
            MethodSecurityExpressionHandler aclExpressionHandler) {

            this.aclExpressionHandler = aclExpressionHandler;
        }

        @Override
        protected MethodSecurityExpressionHandler createExpressionHandler() {
            return aclExpressionHandler;
        }
    }
}

只是为了跟进您自己的回答和我之前的评论,我也对这个评论感到困惑(超过 2 天!)。最后它也覆盖了 configure(AuthenticationManagerBuilder auth) 方法中的 subclass of GlobalMethodSecurityConfiguration 也被注释为 @EnableGlobalMethodSecurity

由于我是 Spring 的新手,很遗憾,我无法提供详细信息来说明为什么会这样,但是如果我将 AuthenticationManagerBuilder 在 class 中扩展 WebSecurityConfigurereAdapter (就像我原来的那样)然后我总是得到IllegalStateException: No ServletContext set 错误。

这是我使用的最终代码:

@Configuration
@EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true )
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {


@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
    return defaultMethodSecurityExpressionHandler();

}

@Override
protected void configure( final AuthenticationManagerBuilder authenticationManagerBuilder ) throws Exception {
    authenticationManagerBuilder.authenticationProvider( authenticationProvider() );
}


@Bean
public JdbcDaoImpl jdbcDao() {

    JdbcDaoImpl jdbcDao = new JdbcDaoImpl();
    jdbcDao.setDataSource( dataSource() );

    return jdbcDao;
}


@Bean
public DaoAuthenticationProvider authenticationProvider() {

    final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService( jdbcDao() );
    authenticationProvider.setPasswordEncoder( encoder() );

    return authenticationProvider;
}

@Bean
public PasswordEncoder encoder() {
    return new BCryptPasswordEncoder( 11 );
}


@Bean( name = "myDS")
public DataSource dataSource() {

    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setDriverClassName( "com.mysql.cj.jdbc.Driver" );
    dataSource.setUrl( "jdbc:mysql://localhost:3306/java_spring_angular_rest_security_acl2" );
    dataSource.setUsername( "root" );
    dataSource.setPassword( "alphax" );

    return dataSource;
}

@Bean
public EhCacheBasedAclCache aclCache() {
    return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
}

@Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {

    EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
    ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
    ehCacheFactoryBean.setCacheName("aclCache");
    return ehCacheFactoryBean;
}

@Bean
public EhCacheManagerFactoryBean aclCacheManager() {
    return new EhCacheManagerFactoryBean();
}


@Bean
public LookupStrategy lookupStrategy() {
    return new BasicLookupStrategy(dataSource(), aclCache(), aclAuthorizationStrategy(), permissionGrantingStrategy());
}

@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
    return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMIN"));
}


@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
    return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
}


@Bean( name = "defaultMethodSecurityExpressionHandler")
public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {

    DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
    AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService());

    expressionHandler.setPermissionEvaluator(permissionEvaluator);
    expressionHandler.setPermissionCacheOptimizer( new AclPermissionCacheOptimizer( aclService() ) );

    return expressionHandler;
}

@Bean( name = "myAclService")
public JdbcMutableAclService aclService() {

    JdbcMutableAclService jdbcMutableAclService =  new JdbcMutableAclService(dataSource(), lookupStrategy(), aclCache());
    jdbcMutableAclService.setClassIdentityQuery( "SELECT @@IDENTITY" );
    jdbcMutableAclService.setSidIdentityQuery( "SELECT @@IDENTITY" );

    return jdbcMutableAclService;
}

}

这里是 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.fireduptech.spring.rest</groupId>
<artifactId>hero</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>hero</name>
<description>Demo project for Spring Boot</description>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>6.0.6</version>
    </dependency>


    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>


            <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-dbcp2</artifactId>
        <version>2.2.0</version>
    </dependency>


    <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-acl -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-acl</artifactId>
        <version>5.0.1.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>5.0.3.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache-core -->
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-core</artifactId>
        <version>2.6.11</version>
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

希望对某人有所帮助 :)