【问题标题】:Spring Boot with AclPermissionEvaluator resulting in IllegalStateException: No ServletContext set带有 AclPermissionEvaluator 的 Spring Boot 导致 IllegalStateException:未设置 ServletContext
【发布时间】:2018-06-30 06:11:47
【问题描述】:

专家您好,

我目前正在学习 Spring Boot,我想将它与 Spring Security ACL 一起使用。 遵循 Spring Security 的 documentationBaeldung.com 的教程,我想我对需要什么有所了解。我还查看了DMS example of Spring。我通过寻找解决方案偶然发现了另一个example

根据这些信息,我已经构建了我的应用程序。作为参考,您可以在GitHub找到当前的应用程序。

当前问题

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

链接到pastebin 上的完整堆栈跟踪。

到目前为止我做了什么

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

我的环境

  • macOS 10.12.6
  • jdk1.8.0_144
  • IntelliJ IDEA 2017.3.3(终极版)
  • Spring Boot v1.5.9.RELEASE

相关项目文件

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

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

感谢和最好的问候 莫里茨

【问题讨论】:

  • 没有调试您的问题,但看起来您需要属于 spring-boot-starter-web 的 ServletContext bean。我没有发现你的 pom.xml 中有 spring-boot-starter-web 依赖项。
  • 嗨@Rakesh,感谢您的评论。将 spring-boot-starter-web 依赖项显式添加到 pom.xml 并没有帮助。问题仍然存在。 spring-boot-starter-data-rest 已隐式加载 spring-boot-starter-web 作为依赖项。

标签: java spring spring-boot spring-security spring-security-acl


【解决方案1】:

大家好,

我自己设法解决了这个问题。我一步一步地评论/取消评论所有MethodSecurity 相关的东西。

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

然后,我创建了一个新类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 天的确切问题(我是 Spring Boot 的新手)。我的 pom.xml 与您的非常相似,但如果我使用 spring-security-config 它会完全破坏它。当我删除它时,我最终会遇到与您相同的错误,并且只能通过注释掉 AclPermissionEvaluator 创建行来启动 Spring Boot,这违背了它的要点。我尝试了您的解决方案,但仍然出现相同的错误。尝试了无数网站,但对于一个新人来说,我觉得它完全是压倒性的。还有其他设置 servlet 上下文顺序的方法吗?试过@order
  • 谢谢,这对我帮助很大。但为了使它工作,我必须在应用程序属性中添加一行:spring.main.allow-bean-definition-overriding=true。如果不这样做,我会收到 bean 'methodSecurityInterceptor' 已定义的错误消息。
【解决方案2】:

只是为了跟进您自己的答案和我之前的评论,我也迷失了这个答案(超过 2 天!)。最后,它还覆盖了 GlobalMethodSecurityConfiguration 子类中的 configure(AuthenticationManagerBuilder auth) 方法,该方法也是用 @EnableGlobalMethodSecurity 注释。

由于我是 Spring 新手,很遗憾我无法详细说明为什么会这样,但是如果我将 AuthenticationManagerBuilder 的配置放在一个类中扩展 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>

希望对某人有所帮助:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-26
    • 2017-12-20
    • 1970-01-01
    • 2018-02-14
    • 2020-08-27
    相关资源
    最近更新 更多