Spring Security hasRole() not working

69.7k views Asked by At

I'm facing a problem when using Spring Security && Thymeleaf, specifically when trying to use the hasRole expression. The 'admin' user has a role 'ADMIN' but hasRole('ADMIN') resolves to false anyway I try it

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

results in:

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

I have enabled use-expressions in my security.xml file

<security:http auto-config="true" use-expressions="true">

And also included the SpringSecurityDialect in my config

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

All the necessary dependencies in my pom.xml file

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

RoleType

public enum RoleType {

    ADMIN 
}

And Userhas a Set of Roles

Why is hasRole() not working?

I appreciate your help, thank you

Workaround

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

12

There are 12 answers

4
Dmitry Stolbov On BEST ANSWER

Try use hasAuthority instead hasRole inside HTML-tag.

sec:authorize="hasAuthority('ADMIN')"
2
Faraj Farook On

Refer the offcial documentation. 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>

Can you simply try it as below without the ${ ... }.

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

I believe you have not prefixed the roles with ROLE_. If so makesure to add the prefix as well like below

<div sec:authorize="hasRole('ROLE_ADMIN')">IS ADMIN</div>
0
Huy Quang On

You are missing a concept:

  • If you use hasRole('ADMIN'), in your ADMIN Enum must be ROLE_ADMIN instead of ADMIN.
  • If you use hasAuthority('ADMIN'), your ADMIN Enum must be ADMIN.

In spring security, hasRole() is the same as hasAuthority(), but hasRole() function map with Authority without ROLE_ prefix.

You can find the accepted answer in this post: Difference between Role and GrantedAuthority in Spring Security

0
Shehan Simen On

In Spring boot 2, You can use either hasRole() or hasAuthority(). The difference is that, you have to user ROLE_ for hasAusthority() method. So for the ROLE_ADMIN,

 @PreAuthorize("hasRole('ADMIN')") == @PreAuthorize("hasAuthority('ROLE_ADMIN')")
0
bloowper On

I had similar problem and i worked around it.

I use the following entities

user entity


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

role entity

    @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<>();
    
    
    }

authority entity


    @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<>();
    
    
    }

bootstraping

        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("[email protected]").
                build()
        );


        userRepository.save(User.builder().
                role(customer).
                username("user").
                password(passwordEncoder.encode("user")).
                email("[email protected]").
                build()
        );

reason why for me working hasAuthority() and hasRole() is fragment from user entity in getAuthorities method

        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;

when u have authority with name ROLE_NAMEOFROLE spring treat it like role when prefix don't exist spring treat it like authority.

remember to have as well authority : "ROLE_ADMIN"

I'm not sure that's the correct approach !!!

0
pperez On

after weeks of trial and error, this worked for me:

Upgrading to the latest version according to https://mvnrepository.com/

Spring Boot Starter Thymeleaf Extras Spring Security 5 Thymeleaf for Spring Boot

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
        <version>2.3.0.RELEASE</version>
    </dependency>

Really donĀ“t know wich version of the dependencies work well with other versions, but for now (May 19th 2020) it worked for me.

Hope it can help someone

0
rohtakdev On

I had to do something similar where i needed to verify the user role. I did below

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

Hope it helps someone.

0
sirandy On

I've recently had the same problem. What you need to do is:

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

(You can change between springsecurity4 or springsecurity3 depending on what you are using).

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

But you also can define as alexsource says: Spring security and Thymeleaf doesn't work

I hope this works for you! Greetings!

0
Manali vidhate On

@Component public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws ServletException, IOException {
        CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();

        String redirectURL = request.getContextPath();
        if (userDetails.hasRole("ROLE_ADMIN")) {
            redirectURL = "admin-dashboard";
        } else if (userDetails.hasRole("ROLE_EMPLOYEE")) {
            redirectURL = "dashboard";
        } else if (userDetails.hasRole("ROLE_TRAINEE")) {
            redirectURL = "dashboard";
        }
        response.sendRedirect(redirectURL);
    }

}
0
drac_o On

In my case, hasRole was not working for the endpoints of a specific controller, while it would work for other controller endpoints.

I realized that this controller had @RequestMapping after @RestController.

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

I changed the order and hasRole was now working:

@RequestMapping("/test/v1")
@RestController
public class TestController {
}
0
Jeffrey B. On

I've had the same issue upgrading from Spring Security 3.x to 4.x. Changing hasRole() to hasAuthority() did the trick for me.

http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#el-common-built-in

0
Shravan Ramamurthy On

I got into the same issue, its because of spring-security 4.0. Due to some reason thymeleaf-extras-springsecurity4 is not compatible with spring-security 4.0 and thymeleaf 2.x. So i downgraded spring-security versions to 3.2.9.RELEASE and it started working. If you still want to use spring-security 4.0, then may be you can try uplifting thymeleaf-extras-springsecurity4 to 3.0.0.RELEASE and thymeleaf verison to 3.0

Or If you are using spring boot app, then the situation becomes more trickier, then the only option left would be to either downgrade the spring-security or upgrade the spring boot version to 1.4.x (which is still in BETA)

In your specific scenario, making the below pom changes should make hasRole working

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