Spring Boot with AclPermissionEvaluator resulting in IllegalStateException: No ServletContext set
Asked Answered
D

2

2

Hello experts,

I'm currently learning Spring Boot and I want to use it with Spring Security ACL. Following the 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 by searching for a solution.

Based on this information, I have build my application. For reference, you can find the current application on GitHub.

The current issue

When I start the application, I'm getting a java.lang.IllegalStateException: No ServletContext set thrown. As far as I understand, this is due to the fact that during Spring Boot's auto-configure magic, my @Configuration annotated classes are initialized before the ServletContext is initialized.

Link to full stack trace on pastebin.

What I have done so far

Based on my research (mostly on StackOverflow) and my current understanding of the issue, it should help to put the responsible Beans into an own @Configuration annotated class. Unfortunately, here I'm currently lost. Related questions led me to this thinking (see this question, or this one.)

My environment

  • macOS 10.12.6
  • jdk1.8.0_144
  • IntelliJ IDEA 2017.3.3 (Ultimate Edition)
  • Spring Boot v1.5.9.RELEASE

Relevant project files

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>[email protected]</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;
        }
    }
}

Thanks a lot for all input! If any further information is required, I will happily provide it.

Thanks and best regards Moritz

Debit answered 21/1, 2018 at 14:0 Comment(2)
Did not debug your issue but looks like you need ServletContext bean which belongs to spring-boot-starter-web. I don't find that you having spring-boot-starter-web dependency in your pom.xml.Canvasback
Hi @Rakesh, thanks for your comment. Explicitly adding spring-boot-starter-web dependency to the pom.xml didn't help. The issue still occurs. spring-boot-starter-data-rest implicitly already loads spring-boot-starter-web as a dependency.Debit
D
2

Hey all,

I managed to resolve the issue myself. I did a step-by-step approach with commenting/uncommenting all the MethodSecurity related stuff.

I pin-pointed it down to the creation of the DefaultMethodSecurityExpressionHandler. This was causing the IllegalStateException: No ServletContext set.

Then, I have created a new Class MethodSecurityConfig.java and I put all the related code there. Now the application is starting again and I can continue with the developed.

@Rakesh: Thanks for your input!

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;
        }
    }
}
Debit answered 22/1, 2018 at 19:11 Comment(2)
Thanks for posting this. I have had this exact issue for 2 days solid now (am new to Spring boot). My pom.xml is very similar to yours but if I use spring-security-config it completely breaks it. When I remove that I end up with the same error you have and can only get Spring Boot started by commenting out the AclPermissionEvaluator creation line, which defeats the point of it. I tried your solution but still left with the same error. Have tried countless website but for a new person to this I find it completely overwhelming. Any other ways of setting servlet context order? Have tried @orderGreer
Thank you, this helped me very much. But in order to make it work I had to add one line to the application properties: spring.main.allow-bean-definition-overriding=true. When not doing so, I get the error message that bean 'methodSecurityInterceptor' is already defined.Naima
G
2

Just to follow up on your own answer and my previous comment where I was also lost with this one (for over 2 days!). In the end it was also overriding the configure(AuthenticationManagerBuilder auth) method in a subclass of GlobalMethodSecurityConfiguration which was also annotated with @EnableGlobalMethodSecurity.

As I am new to Spring I am unfortunately unable to provide details as to why this is the way but if I put the configuring of the AuthenticationManagerBuilder in a class that extends WebSecurityConfigurereAdapter (like I originally had) then I always end up getting a IllegalStateException: No ServletContext set error.

Here is the final code I used:

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

}

and here is the 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>

Hope that helps someone :)

Greer answered 12/2, 2018 at 21:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.