Spring 4、Thymeleaf,安全性:未找到 WebApplicationContext:未注册 ContextLoaderListener
Spring 4, Thymeleaf, Security: No WebApplicationContext found: no ContextLoaderListener registered
这让我发疯...所有有效的方法,我不得不从更多不太好的例子中痛苦地搜索,现在我被一些应该开箱即用的东西困住了。
重要:我用的是Java配置,所以根本就没有XML。
摘自 build.gradle
dependencies {
compile 'org.springframework:spring-webmvc:4.2.3.RELEASE',
'org.thymeleaf:thymeleaf-spring4:2.1.4.RELEASE',
'com.fasterxml.jackson.core:jackson-databind:2.6.3',
'com.google.guava:guava:18.0',
'org.springframework.security:spring-security-config:4.0.3.RELEASE',
'org.springframework.security:spring-security-web:4.0.3.RELEASE',
'org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.2.RELEASE'
}
有趣的部分来自 WebMvcConfigurationAdapter
@Configuration
@ComponentScan("de.mypackage")
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
@Bean
IDialect springSecurityDialect() {
return new SpringSecurityDialect();
}
@Bean
@Inject
SpringTemplateEngine templateEngine(final CustomTemplateResolver customTemplateResolver,
final ServletContextTemplateResolver servletContextTemplateResolver,
final IDialect springSecurityDialect) {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(customTemplateResolver);
templateEngine.addTemplateResolver(servletContextTemplateResolver);
templateEngine.addDialect(springSecurityDialect);
return templateEngine;
}
}
必须的WebApplicationInitializer
public final class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(final ServletContext servletContext) {
log.debug("start up {}", servletContext);
try (
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext()) {
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
final Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/");
}
}
}
我的 WebSecurityConfigurerAdapter
版本
@Configuration
@EnableWebSecurity
public class CustomWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
private static final String LOGIN_URL = "/#login";
@Inject UserDetailsService userService;
@Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.anonymous().principal(getAnonymousUser()).authorities("ROLE_ANOYMOUS");
final String adminAccess = String.format("hasAnyRole('ROLE_%s', 'ROLE_%s')",
Role.SYSTEM_ADMINISTRATOR, Role.USER_ADMINISTRATOR);
log.debug("Configure admin access: {}", adminAccess);
http.authorizeRequests().antMatchers("/admin/**").access(adminAccess).and().formLogin()
.loginPage(LOGIN_URL);
final String systemAdminAccess = String.format("hasRole('ROLE_%s')", Role.SYSTEM_ADMINISTRATOR);
log.debug("Configure system admin access: {}", systemAdminAccess);
http.authorizeRequests().antMatchers("/rest/templates/**").access(systemAdminAccess).and()
.formLogin().loginPage(LOGIN_URL);
}
Principal getAnonymousUser() {
final Principal principal = () -> "guest";
return principal;
}
@Bean
AuthenticationManager getAuthenticationManager() throws Exception {
log.debug("Authentication manager configured");
return authenticationManager();
}
@Bean
PasswordEncoder passwordEncoder() {
final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(12);
return passwordEncoder;
}
}
后来发现,这个模板没有用
<meta th:name="${_csrf.parameterName}" th:content="${_csrf.token}" />
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
解决方案是创建类型为 AbstractSecurityWebApplicationInitializer
的 empty class
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {}
现在这也起作用了。
但是,一如既往,下一个问题只需要一步。
这工作正常:
<li th:if="${#authentication.name == 'guest'}"><a href="#login">Anmelden</a></li>
但我更喜欢检查角色而不是用户,这会导致错误:
<li th:if="${#authorization.expression('hasRole(''ROLE_ANOYMOUS'')')}"><a href="#login">Anmelden</a></li>
Trace 摘要:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#authorization.expression('hasRole(''ROLE_ANOYMOUS'')')" (main/header:11)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
caused by
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#authorization.expression('hasRole(''ROLE_ANOYMOUS'')')" (main/header:11)
org.thymeleaf.spring4.expression.SpelVariableExpressionEvaluator.evaluate(SpelVariableExpressionEvaluator.java:161)
causef by
java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext(WebApplicationContextUtils.java:83)
我没有想法所以票价...
又是少了一小块...
public final class WebAppInitializer implements WebApplicationInitializer {
private static final Logger log = LoggerFactory.getLogger(WebAppInitializer.class);
@Override
public void onStartup(final ServletContext servletContext) {
log.debug("start up {}", servletContext);
try (
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext()) {
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
servletContext.addListener(new ContextLoaderListener(ctx));
final Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/");
}
}
}
看看servletContext.addListener
这让我发疯...所有有效的方法,我不得不从更多不太好的例子中痛苦地搜索,现在我被一些应该开箱即用的东西困住了。
重要:我用的是Java配置,所以根本就没有XML。
摘自 build.gradle
dependencies {
compile 'org.springframework:spring-webmvc:4.2.3.RELEASE',
'org.thymeleaf:thymeleaf-spring4:2.1.4.RELEASE',
'com.fasterxml.jackson.core:jackson-databind:2.6.3',
'com.google.guava:guava:18.0',
'org.springframework.security:spring-security-config:4.0.3.RELEASE',
'org.springframework.security:spring-security-web:4.0.3.RELEASE',
'org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.2.RELEASE'
}
有趣的部分来自 WebMvcConfigurationAdapter
@Configuration
@ComponentScan("de.mypackage")
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
@Bean
IDialect springSecurityDialect() {
return new SpringSecurityDialect();
}
@Bean
@Inject
SpringTemplateEngine templateEngine(final CustomTemplateResolver customTemplateResolver,
final ServletContextTemplateResolver servletContextTemplateResolver,
final IDialect springSecurityDialect) {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(customTemplateResolver);
templateEngine.addTemplateResolver(servletContextTemplateResolver);
templateEngine.addDialect(springSecurityDialect);
return templateEngine;
}
}
必须的WebApplicationInitializer
public final class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(final ServletContext servletContext) {
log.debug("start up {}", servletContext);
try (
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext()) {
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
final Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/");
}
}
}
我的 WebSecurityConfigurerAdapter
版本@Configuration
@EnableWebSecurity
public class CustomWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
private static final String LOGIN_URL = "/#login";
@Inject UserDetailsService userService;
@Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.anonymous().principal(getAnonymousUser()).authorities("ROLE_ANOYMOUS");
final String adminAccess = String.format("hasAnyRole('ROLE_%s', 'ROLE_%s')",
Role.SYSTEM_ADMINISTRATOR, Role.USER_ADMINISTRATOR);
log.debug("Configure admin access: {}", adminAccess);
http.authorizeRequests().antMatchers("/admin/**").access(adminAccess).and().formLogin()
.loginPage(LOGIN_URL);
final String systemAdminAccess = String.format("hasRole('ROLE_%s')", Role.SYSTEM_ADMINISTRATOR);
log.debug("Configure system admin access: {}", systemAdminAccess);
http.authorizeRequests().antMatchers("/rest/templates/**").access(systemAdminAccess).and()
.formLogin().loginPage(LOGIN_URL);
}
Principal getAnonymousUser() {
final Principal principal = () -> "guest";
return principal;
}
@Bean
AuthenticationManager getAuthenticationManager() throws Exception {
log.debug("Authentication manager configured");
return authenticationManager();
}
@Bean
PasswordEncoder passwordEncoder() {
final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(12);
return passwordEncoder;
}
}
后来发现,这个模板没有用
<meta th:name="${_csrf.parameterName}" th:content="${_csrf.token}" />
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
解决方案是创建类型为 AbstractSecurityWebApplicationInitializer
的 empty classimport org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {}
现在这也起作用了。
但是,一如既往,下一个问题只需要一步。
这工作正常:
<li th:if="${#authentication.name == 'guest'}"><a href="#login">Anmelden</a></li>
但我更喜欢检查角色而不是用户,这会导致错误:
<li th:if="${#authorization.expression('hasRole(''ROLE_ANOYMOUS'')')}"><a href="#login">Anmelden</a></li>
Trace 摘要:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#authorization.expression('hasRole(''ROLE_ANOYMOUS'')')" (main/header:11)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
caused by
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#authorization.expression('hasRole(''ROLE_ANOYMOUS'')')" (main/header:11)
org.thymeleaf.spring4.expression.SpelVariableExpressionEvaluator.evaluate(SpelVariableExpressionEvaluator.java:161)
causef by
java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext(WebApplicationContextUtils.java:83)
我没有想法所以票价...
又是少了一小块...
public final class WebAppInitializer implements WebApplicationInitializer {
private static final Logger log = LoggerFactory.getLogger(WebAppInitializer.class);
@Override
public void onStartup(final ServletContext servletContext) {
log.debug("start up {}", servletContext);
try (
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext()) {
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
servletContext.addListener(new ContextLoaderListener(ctx));
final Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/");
}
}
}
看看servletContext.addListener