使用Spring Security进行Thymeleaf授权并不起作用

时间:2017-04-23 22:12:17

标签: java spring-boot spring-security thymeleaf

我在Spring Boot应用程序中使用Spring Security,似乎Thymeleaf授权无法正常工作。

我的Thymeleaf模板包含以下代码:

<div class="container">
    <div class="row" sec:authorize="isAuthenticated()">
        <h2 style="color:green">User is Logged In</h2>
        <p sec:authentication="principal.username">username</p>
    </div>

    <div class="row" sec:authorize="!isAuthenticated()">
        <h2 style="color:red">User is Logged Out</h2>
    </div>

    <div class="row" sec:authorize="hasRole('ROLE_SUPERUSER')">
        <h2>This will only be displayed if authenticated user has role ROLE_SUPERUSER.</h2>
    </div>

    <div class="row" sec:authorize="hasRole('ROLE_ADMIN')">
        <h2>This will only be displayed if authenticated user has role ROLE_ADMIN.</h2>
    </div>

    <div class="row" sec:authorize="hasRole('ROLE_USER')">
        <h2>This will only be displayed if authenticated user has role ROLE_USER.</h2>
    </div>

    <div th:if="${#authorization.expression('hasRole(''ROLE_ADMIN'')')}">
        This will only be displayed if authenticated user has role ROLE_ADMIN.
    </div>

    <div th:if="${#authorization.expr('hasRole(''ROLE_ADMIN'')')}">
        This will only be displayed if authenticated user has role ROLE_ADMIN.
    </div>
</div>

示例来自:https://github.com/thymeleaf/thymeleaf-extras-springsecurity

但是,显示的唯一内容是sec:authorize="isAuthenticated()"sec:authorize="!isAuthenticated()",无论用户的角色如何,都会忽略授权。

我的百万美元配置是:

@Configuration
public class ThymeleafConfig {

    @Bean
    public TemplateResolver defaultTemplateResolver() {
        TemplateResolver resolver = new TemplateResolver();
        resolver.setResourceResolver(thymeleafResourceResolver());
        resolver.setPrefix("classpath:/templates/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode("HTML5");
        resolver.setCharacterEncoding("UTF-8");
        resolver.setCacheable(true);
        return resolver;
    }

    @Bean
    public SpringResourceResourceResolver thymeleafResourceResolver() {
        return new SpringResourceResourceResolver();
    }

    @Bean
    public SpringTemplateEngine templateEngine(TemplateResolver templateResolver) {
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(templateResolver);
        engine.addDialect(new SpringSecurityDialect());
        engine.addDialect(new LayoutDialect());
        return engine;
    }

    @Bean
    public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine);
        resolver.setCharacterEncoding("UTF-8");
        resolver.setContentType("text/html");
        resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
        return resolver;
    }

}

我对thymeleaf-extras-springsecurity4使用以下依赖项:

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

版本3.0.2.RELEASE根本不起作用,Thymeleaf总是忽略sec名称空间。
我的Spring Boot版本是1.5.2.RELEASE

可能是什么原因?

更新。 configure(HttpSecurity http)中的SecurityConfig方法如下所示:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().ignoringAntMatchers("/h2-console").disable()
            .authorizeRequests()
                .antMatchers("/webjars/**", "/static/**", "/images/**", "/**/favicon.ico").permitAll()
                .antMatchers("/heat/**", "/power/**", "/water/**").permitAll()

            // start allowing h2-console
                .antMatchers("/h2-console/**").permitAll();
            http.csrf().disable();
            http.headers().frameOptions().disable()
            // end allowing h2-console

            .and().authorizeRequests().antMatchers("/info").permitAll()
            .and().authorizeRequests().antMatchers("/users/**").authenticated()
            .and().authorizeRequests().antMatchers("/users/**").hasAnyAuthority("ADMIN", "SUPERUSER")

            .and().formLogin()
                    .loginPage("/login")
                    .permitAll()
            .and()
                    .logout()
                    .permitAll()
                    .deleteCookies("remove")
                    .logoutUrl("/logout")
                    .logoutSuccessUrl("/")
                    .invalidateHttpSession(true)

            .and().exceptionHandling().accessDeniedPage("/access_denied");
}

来自IndexController的映射非常简单,只返回login模板:

@RequestMapping("/login")
public String loginForm() {
    return "login";
}

3 个答案:

答案 0 :(得分:9)

解决任务的另一种方法是使用此语法来检查角色:

<div class="row" th:if="${#request.isUserInRole('SUPERUSER')}">
    <h2>This will only be displayed if authenticated user has role ROLE_SUPERUSER.</h2>
</div>

它不使用sec命名空间,实际上根本不需要使用thymeleaf-extras-springsecurity4依赖。

答案 1 :(得分:1)

Things to try:

1) Annotate your configuration with @EnableWebMvc.

2) Replace ROLE_ADMIN with just ADMIN (and the others correspondingly too).

3) In your controller, print this to see your current roles:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

Set<String> roles = authentication.getAuthorities().stream()
     .map(r -> r.getAuthority()).collect(Collectors.toSet());

System.out.println(roles);

If that doesn't work for you, perhaps try getUserPrincipal() from HttpServletRequest.

Short of that:

I am including my MVC config so that you can try the latest Thymeleaf and Spring Security versions. There are some extra configurations in there, so you can remove what's not relevant to your project.

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configuration) {

        configuration.enable();
    }

    @Bean
    public ThymeleafViewResolver viewResolver() {

        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setOrder(1);
        resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
        resolver.setTemplateEngine(templateEngine());
        return resolver;
    }

    @Bean
    public TemplateEngine templateEngine() {

        Set<ITemplateResolver> templateResolvers = new LinkedHashSet<>(1);
        templateResolvers.add(webTemplateResolver());

        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolvers(templateResolvers);
        Set<IDialect> dialects = new LinkedHashSet<>(2);
        dialects.add(new SpringSecurityDialect());
        dialects.add(new Java8TimeDialect());
        templateEngine.setAdditionalDialects(dialects);
        return templateEngine;
    }

    @Bean
    public ITemplateResolver webTemplateResolver() {

        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setPrefix("/WEB-INF/thymeleaf/");
        resolver.setTemplateMode(TemplateMode.HTML);
        resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
        resolver.setSuffix(".html");
        resolver.setCacheable(false);
        resolver.setOrder(2);
        return resolver;
    }

    @Bean
    public ViewResolver tilesViewResolver() {

        UrlBasedViewResolver viewResolver = new UrlBasedViewResolver();
        viewResolver.setViewClass(TilesView.class);
        viewResolver.setOrder(0);
        return viewResolver;
    }

    @Bean
    public TilesConfigurer tilesConfigurer() {

        TilesConfigurer configurer = new TilesConfigurer();
        configurer.setDefinitions("/WEB-INF/**/views.xml");
        return configurer;
    }

    @Bean
    public LocalValidatorFactoryBean validator() {

        return new LocalValidatorFactoryBean();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {

        registry.addViewController("/403");
        registry.addViewController("/404");
        registry.addViewController("/about");
        //edited for brevity
    }

    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {

        ReloadableResourceBundleMessageSource source = new ReloadableResourceBundleMessageSource();
        source.setBasename("classpath:messages");
        source.setDefaultEncoding(StandardCharsets.UTF_8.name());
        return source;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(localeChangeInterceptor());
        registry.addInterceptor(themeChangeInterceptor());
        registry.addInterceptor(deviceResolverHandlerInterceptor());
        super.addInterceptors(registry);
    }

    @Bean
    public HandlerInterceptor localeChangeInterceptor() {

        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang");
        return interceptor;
    }

    @Bean
    public HandlerInterceptor themeChangeInterceptor() {

        ThemeChangeInterceptor interceptor = new ThemeChangeInterceptor();
        interceptor.setParamName("theme");
        return interceptor;
    }

    @Bean
    public ResourceBundleThemeSource themeSource() {

        ResourceBundleThemeSource themeSource = new ResourceBundleThemeSource();
        themeSource.setBasenamePrefix("theme-");
        return themeSource;
    }

    @Bean
    public PersistedThemeResolver themeResolver() {

        PersistedThemeResolver resolver = new PersistedThemeResolver();
        resolver.setDefaultThemeName("default");
        return resolver;
    }

    @Bean
    public HandlerInterceptor deviceResolverHandlerInterceptor() {

        return new DeviceResolverHandlerInterceptor();
    }

    @Bean
    public CookieLocaleResolver localeResolver() {

        CookieLocaleResolver resolver = new CookieLocaleResolver();
        resolver.setDefaultLocale(Locale.US);
        return resolver;
    }

    //removed custom bean declaration

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {

        argumentResolvers.add(new ServletWebArgumentResolverAdapter(new DeviceWebArgumentResolver()));
        super.addArgumentResolvers(argumentResolvers);
    }

    @Bean
    public Executor taskExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(100);
        executor.initialize();
        return executor;
    }
}

Working pom excerpt:

    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf</artifactId>
        <version>3.0.2.RELEASE</version>
        <exclusions>
            <exclusion>
                <groupId>javassist</groupId>
                <artifactId>javassist</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring4</artifactId>
        <version>3.0.2.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity4</artifactId>
        <version>3.0.0.RELEASE</version>
    </dependency>

答案 2 :(得分:1)

经过大量不同配置的尝试后,我找到了解决方法。在这种情况下,sec:authorize="hasAuthority('ADMIN')"属性有效:

<div class="row" sec:authorize="hasRole('ROLE_ADMIN')">
    <div class="col-md-10 col-md-offset-2">
        <h2>User Has Role Admin</h2>
    </div>
</div>
<div class="row" sec:authorize="hasAuthority('ADMIN')">
    <div class="col-md-10 col-md-offset-2">
        <h2>User Has Authority Admin</h2>
    </div>
</div>
页面上有

User Has Authority Admin标题呈现。

仍然不知道为什么sec:authorize="hasRole('ROLE_ADMIN')"属性不起作用,因为它在thymeleaf-extras-springsecurity GitHub页面上被建议作为示例: https://github.com/thymeleaf/thymeleaf-extras-springsecurity#using-the-attributes

希望这可以帮助某人,尽管问题仍然存在。