Spring Security hasRole() not working

JavaSpringSpring MvcSpring SecurityThymeleaf

Java Problem Overview


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')}"

Java Solutions


Solution 1 - Java

Try use hasAuthority instead hasRole inside HTML-tag.

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

Solution 2 - Java

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

Solution 3 - Java

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

Solution 4 - Java

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.

Solution 5 - Java

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: https://stackoverflow.com/questions/23348341/spring-security-and-thymeleaf-doesnt-work

I hope this works for you! Greetings!

Solution 6 - Java

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>

Solution 7 - Java

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>

Solution 8 - Java

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

Solution 9 - Java

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

Solution 10 - Java

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

Solution 11 - Java

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

}

Solution 12 - Java

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionXipoView Question on Stackoverflow
Solution 1 - JavaDmitry StolbovView Answer on Stackoverflow
Solution 2 - JavaHuy QuangView Answer on Stackoverflow
Solution 3 - JavaJeffrey B.View Answer on Stackoverflow
Solution 4 - JavarohtakdevView Answer on Stackoverflow
Solution 5 - JavasirandyView Answer on Stackoverflow
Solution 6 - JavaShravan RamamurthyView Answer on Stackoverflow
Solution 7 - JavaFaraj FarookView Answer on Stackoverflow
Solution 8 - JavapperezView Answer on Stackoverflow
Solution 9 - JavaShehan SimenView Answer on Stackoverflow
Solution 10 - JavaTomek OrchowskiView Answer on Stackoverflow
Solution 11 - JavaManali vidhateView Answer on Stackoverflow
Solution 12 - Javadrac_oView Answer on Stackoverflow