使用 Spring Security 的 Thymeleaf 授权不起作用[重复]

Posted

技术标签:

【中文标题】使用 Spring Security 的 Thymeleaf 授权不起作用[重复]【英文标题】:Thymeleaf authorization with Spring Security doesn't work [duplicate] 【发布时间】:2017-09-20 11:37:55 【问题描述】:

我在我的 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;
    


我对@9​​87654322@ 使用以下依赖项:

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

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

可能是什么原因?

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

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

【问题讨论】:

据我所知,角色只是一个带有特殊 ROLE_ 前缀的权限。我的角色没有这个前缀,它们只是USERADMIN 等等:i.stack.imgur.com/jpBA1.png 当我使用hasRole 时,我会在我的角色前面加上ROLE_,但表中的角色名称本身没有这个前缀。 @DimaSan:这就是重点。如果使用hasRole,则必须在数据库中使用前缀。但是您可以配置不同的前缀或使用hasAuthority(如您的回答)。 是的,您是对的,现在我可以确认它可以按照您的描述工作。那么,让角色名称带有或不带有 ROLE_ 前缀的最佳做法是什么? 我只使用带前缀的角色名,因为我不喜欢太多的自定义配置。我尽量使用默认值。但是 Spring 开发人员会说,两者皆有可能是你的决定。 【参考方案1】:

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

<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 依赖项来使用它。

【讨论】:

谢谢你的建议,但结果是一样的,Thymeleaf 完全忽略了用户的角​​色。顺便说一句,右边是th:if="$#httpServletRequest.isUserInRole('SUPERUSER')" @DimaSan 嗯,可能是版本不匹配。 你是说 Thymelaf 版本吗?我使用 spring-boot-starter-thymeleaf 依赖和 Thymeleaf 版本有 2.1.5.RELEASE。 感谢您的回答。无论我尝试什么,我都无法获得 sec:authorize 工作。它总是返回 true。不过你回答得很完美。 这实际上对我的 spring mvc 和 spring security 4.2.3 有效【参考方案2】:

要尝试的事情:

1) 使用 @EnableWebMvc 注释您的配置。

2) 仅将 ROLE_ADMIN 替换为 ADMIN(以及其他相应的)。

3) 在您的控制器中,打印此内容以查看您当前的角色:

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

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

System.out.println(roles);

如果这对您不起作用,不妨试试 getUserPrincipal()HttpServletRequest

简短:

我包含了我的 MVC 配置,以便您可以尝试最新的 Thymeleaf 和 Spring Security 版本。那里有一些额外的配置,因此您可以删除与您的项目无关的内容。

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

工作 pom 摘录:

    <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>

【讨论】:

不幸的是,使用您的WebMvcConfig 配置,行为是相同的:权限与antMatchers 一起使用(以便管理员可以访问.authorizeRequests().antMatchers("/users/**").hasAnyAuthority("ADMIN", "SUPERUSER") 页面,在SpringSecurityConfig 中配置)但它们没有呈现在带有 sec:authorize 标签的 Thymeleaf 模板中。正如我所料,角色也是从控制器打印的,管理员有USERADMIN 角色。 thymeleaf-extras-springsecurity4 肯定有问题。无论如何,非常感谢您的帮助。 好吧,完全是猜测,但我用我的 POM 中的 sn-p 更新了我的问题。我还没有对 Spring Security 进行小升级,但是这种组合在没有 Boot 的情况下对我有用。这一切都假设sec 命名空间当然是在 HTML 文件的顶部定义的。【参考方案3】:

经过多次尝试不同配置后,我找到了一种解决方法。在这种情况下,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

希望这可以帮助某人,尽管问题仍然悬而未决。

【讨论】:

以上是关于使用 Spring Security 的 Thymeleaf 授权不起作用[重复]的主要内容,如果未能解决你的问题,请参考以下文章

带有 Cordova 默认应用程序 XML 错误的 Eclipse THyM

Spring websocket在线聊天室

Spring Framework,Spring Security - 可以在没有 Spring Framework 的情况下使用 Spring Security?

仅使用 Spring-Security(或)Spring 进行授权

在使用 Oauth、SAML 和 spring-security 的多租户的情况下从 spring-security.xml 中获取错误

未调用 Spring Security j_spring_security_check