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 Bean
s 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
spring-boot-starter-web
dependency to thepom.xml
didn't help. The issue still occurs.spring-boot-starter-data-rest
implicitly already loadsspring-boot-starter-web
as a dependency. – Debit