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