Spring Security 的 hasRole() 方法无法正常工作

53

我在使用Spring Security和Thymeleaf时遇到了问题,特别是在尝试使用hasRole表达式时。'admin'用户具有'ADMIN'角色,但无论如何尝试,hasRole('ADMIN')都会解析为false。

我的html:

1.<div sec:authentication="name"></div> <!-- works fine -->
2.<div sec:authentication="principal.authorities"></div> <!-- works fine -->

3.<div  sec:authorize="isAuthenticated()" >true</div> <!-- works fine -->
4.<span th:text="${#authorization.expression('isAuthenticated()')}"></span> <!-- works fine -->

5.<div th:text="${#vars.role_admin}"></div> <!--Works fine -->
6.<div  sec:authorize="${hasRole('ADMIN')}" > IS ADMIN </div> <!-- Doesnt work -->
7.<div  sec:authorize="${hasRole(#vars.role_admin)}" > IS ADMIN </div> <!-- Doesnt work -->
8.<div th:text="${#authorization.expression('hasRole(''ADMIN'')')} "></div> <!-- Doesnt work -->
9.<div th:text="${#authorization.expression('hasRole(#vars.role_admin)')}"></div> <!-- Doesnt work -->

结果为:

1.admin
2.[ADMIN]
3.true
4.true
5.ADMIN
6."prints nothing because hasRole('ADMIN') resolves to false"
7."prints nothing because hasRole(#vars.role_admin) resolves to false"
8.false
9.false

我已在我的security.xml文件中启用了use-expressions
<security:http auto-config="true" use-expressions="true">

我还在我的配置中包含了SpringSecurityDialect

<bean id="templateEngine"
      class="org.thymeleaf.spring4.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver" />  
    <property name="additionalDialects">
        <set>
            <bean class="org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect" />
        </set>
    </property>      
</bean>

在我的pom.xml文件中的所有必要依赖项

<!--Spring security--> 
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>4.0.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>4.0.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>4.0.1.RELEASE</version>
    </dependency>        
    
    <!--Thymeleaf Spring Security-->
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity4</artifactId>
        <version>2.1.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>

Role.java

@Entity
@Table(name = "roles")

    public class Role implements Serializable {
    
        @Id
        @Enumerated(EnumType.STRING)
        private RoleType name;
        //... getters, setters
    }

角色类型

public enum RoleType {

    ADMIN 
}

用户拥有一组角色。

为什么hasRole()无法工作?

非常感谢您的帮助,谢谢。

解决方法

th:if="${#strings.contains(#authentication.principal.authorities,'ADMIN')}"


2
尝试在你的测试中使用ROLE_ADMIN而不是ADMIN - woemler
我已经尝试过了,但是从springsecurity4开始只有ADMIN - Xipo
可能的解决方案:https://dev59.com/ho_ea4cB1Zd3GeqPRrOq#40492335 - bpgriner
12个回答

114

尝试在 HTML 标签内使用 hasAuthority 代替 hasRole

sec:authorize="hasAuthority('ADMIN')"

8
我遇到了同样的问题。在从Spring 3转移到Spring 4时,hasRole()似乎无法正常工作。使用hasAuthority()对我有用。@Xipo,您可以确认它是否适用于您并验证此答案吗? - Damien Polegato
我也可以确认这种行为。目前还没有找到解释。 - jplandrain
4
有一个解释:https://dev59.com/MWIk5IYBdhLWcg3wKLWd - Oleg Abrazhaev
这对我不起作用 - <span sec:authentication="principal.authorities">No Authorities Captured</span> 给出 [ROLE_USER,ROLE_ADMIN] 但是<div sec:authorize="hasAuthority(' ROLE_ADMIN')"> This content is only shown to Authority - Admin. </div> 无法运行。 - Ashish Verma

33

你漏了一个概念:

  • 如果你使用hasRole('ADMIN'),你的ADMIN枚举必须是ROLE_ADMIN而不是ADMIN
  • 如果你使用hasAuthority('ADMIN'),你的ADMIN枚举必须是ADMIN

在Spring Security中,hasRole()hasAuthority()是一样的,但是hasRole()函数将权限映射到没有ROLE_前缀的Authority上。

你可以在这篇文章中找到被接受的答案:Spring Security中Role和GrantedAuthority的区别


19

7

我曾经需要验证用户角色,方法如下:

<div th:if="${ #authorization.expression('isAuthenticated()') and #strings.contains(#authentication.principal.authorities,'ADMIN')}">          
    <a th:href="@{/somelink}">ADMIN LINK</a>
 </div>

希望这能帮到某些人。

6

我最近也遇到了同样的问题。 您需要做的是:

  1. In your html add these statements:

    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
    
(您可以根据您使用的版本选择springsecurity4或springsecurity3进行更改。)
  1. Be sure that you have added this resource to your libraries. I'm using gradle but you can do the same with Maven.

    compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.2.RELEASE'
    
  2. In your SpringWebConfiguration class or xml be sure that you are adding the dialect for thymeleaf SpringSecurity: I'm using a java class for the configuration.

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

但是你也可以按照alexsource所说的方式定义:

Spring security和Thymeleaf无法正常工作

希望这对你有用!祝好!


2
我遇到了同样的问题,这是由于spring-security 4.0引起的。由于某些原因,thymeleaf-extras-springsecurity4spring-security 4.0和thymeleaf 2.x不兼容。所以我将spring-security版本降级到3.2.9.RELEASE,然后它开始工作了。
如果您仍想使用spring-security 4.0,则可以尝试将thymeleaf-extras-springsecurity4升级到3.0.0.RELEASE,并将thymeleaf版本升级到3.0
或者,如果您正在使用spring boot应用程序,则情况变得更加棘手,那么唯一的选择就是要么降级spring-security,要么将spring boot版本升级到1.4.x(仍处于BETA阶段)。
在您特定的场景中,进行以下pom更改应该会使hasRole起作用。
<!--Spring security--> 
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>3.2.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>3.2.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>3.2.9.RELEASE</version>
    </dependency>        

    <!--Thymeleaf Spring Security-->
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity4</artifactId>
        <version>2.1.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>

1

请参考官方文档。http://www.thymeleaf.org/doc/articles/springsecurity.html

<div sec:authorize="hasRole('ROLE_ADMIN')">
  This content is only shown to administrators.
</div>
<div sec:authorize="hasRole('ROLE_USER')">
  This content is only shown to users.
</div>

你可以尝试以下方法,不需要使用${ ... }

<div sec:authorize="hasRole('ADMIN')">IS ADMIN</div>

我相信您还没有在角色名前加上ROLE_。如果是这样,请确保像下面这样添加前缀。
<div sec:authorize="hasRole('ROLE_ADMIN')">IS ADMIN</div>

1
在使用安全表达式(在Spring Security 3中引入)时,前缀是不必要的... 当访问条件基于配置属性AccessDecisionVoter评估时,需要使用ROLE_前缀来命名角色。该前缀允许区分角色和其他条件(例如IS_AUTHENTICATED_FULLY)。 - Pavel Horal
1
我尝试过不使用 ${ ... },但仍然无法工作。我对这个问题的临时解决方案是使用 th:if="${#strings.contains(#authentication.principal.authorities,'ADMIN')}" - Xipo

0

我曾经遇到过类似的问题,但最终解决了。

我使用了以下实体:

用户实体


    @Setter
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Entity
    public class User implements UserDetails, CredentialsContainer {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(nullable = false,unique = true)
        private String username;
    
        @Column(nullable = false,unique = true)
        private String email;
    
        private String password;
    
        @Builder.Default
        private Boolean accountNonExpired = true;
    
        @Builder.Default
        private Boolean accountNonLocked = true;
    
        @Builder.Default
        private Boolean credentialsNonExpired = true;
    
        @Builder.Default
        private Boolean enabled = true;
    
        @CreationTimestamp
        @Column(updatable = false)
        private Timestamp createdDate;
    
        @UpdateTimestamp
        private Timestamp lastModifiedDate;
    
        @Singular
        @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
        @JoinTable(
                name = "user_role",
                joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
                inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")
        )
        private Set<Role> roles = new HashSet<>();
    
        @Override
        public void eraseCredentials() {
            this.password = null;
        }
    
        @Override
        @Transient
        public Collection<? extends GrantedAuthority> getAuthorities() {
            Set<SimpleGrantedAuthority> authorities =
                    this.roles.stream().
                    map(Role::getAuthorities).
                    flatMap(Set::stream).
                    map(authority -> new SimpleGrantedAuthority(authority.getPermission())).
                    collect(Collectors.toSet());
    
            roles.stream().map(Role::getName).map(SimpleGrantedAuthority::new).forEach(authorities::add);//WE NEED IT FOR hasRole() functionality
            return authorities;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return accountNonExpired;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return accountNonLocked;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return credentialsNonExpired;
        }
    
        @Override
        public boolean isEnabled() {
            return enabled;
        }
    }

角色实体

    @Setter
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Entity
    public class Role  {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    
        @ManyToMany(mappedBy = "roles")
        private Set<User> users;
    
        @Singular
        @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
        @JoinTable(
                name = "role_authority",
                joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"),
                inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id")
        )
        private Set<Authority> authorities = new HashSet<>();
    
    
    }

权限实体


    @Setter
    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Entity
    public class Authority  {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        Long id;
    
        private String permission;
    
        @Singular
        @ManyToMany(mappedBy = "authorities")
        private Set<Role> roles = new HashSet<>();
    
    
    }

引导程序

        var storeItemCreate = authorityRepository.save(Authority.builder().permission("store.item.create").build());
        var storeItemRead = authorityRepository.save(Authority.builder().permission("store.item.read").build());
        var storeItemUpdate = authorityRepository.save(Authority.builder().permission("store.item.update").build());
        var storeItemDelete = authorityRepository.save(Authority.builder().permission("store.item.delete").build());



        var admin = roleRepository.save(Role.builder().
                authority(storeItemCreate).
                authority(storeItemRead).
                authority(storeItemUpdate).
                authority(storeItemDelete).
                name("ROLE_ADMIN").build());

        var customer = roleRepository.save(Role.builder().
            authority(storeItemRead).
            name("ROLE_CUSTOMER").
            build());

        userRepository.save(User.builder().
                role(admin).
                username("admin").
                password(passwordEncoder.encode("admin")).
                email("admin@admin.com").
                build()
        );


        userRepository.save(User.builder().
                role(customer).
                username("user").
                password(passwordEncoder.encode("user")).
                email("user@user.com").
                build()
        );

我使用hasAuthority()和hasRole()的原因是因为在getAuthorities方法中,这些方法是从用户实体中获取权限片段。

        Set<SimpleGrantedAuthority> authorities =
                this.roles.stream().
                map(Role::getAuthorities).
                flatMap(Set::stream).
                map(authority -> new SimpleGrantedAuthority(authority.getPermission())).
                collect(Collectors.toSet());

        roles.stream().map(Role::getName).map(SimpleGrantedAuthority::new).forEach(authorities::add);//WE NEED IT FOR hasRole() functionality
        return authorities;

当您拥有名为ROLE_NAMEOFROLE的权限时,Spring会将其视为角色。当前缀不存在时,Spring会将其视为权限。

请记得同时拥有权限:"ROLE_ADMIN"

我不确定这是否是正确的方法!!!


0
在Spring Boot 2中,您可以使用hasRole()或hasAuthority()。区别在于,对于hasAuthority()方法,您必须使用ROLE_。因此,对于ROLE_ADMIN,
 @PreAuthorize("hasRole('ADMIN')") == @PreAuthorize("hasAuthority('ROLE_ADMIN')")

0
在我的情况下,hasRole 在特定控制器的端点上无法工作,而在其他控制器的端点上可以正常工作。
我意识到这个控制器在 @RestController 之后有一个 @RequestMapping
@RestController
@RequestMapping("/test/v1")
public class TestController {
}

我改变了顺序,现在hasRole可以工作了:

@RequestMapping("/test/v1")
@RestController
public class TestController {
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接