Spring Security hasRole() not working
Asked Answered
A

12

52

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

Agglomeration answered 11/6, 2015 at 17:58 Comment(3)
Try using ROLE_ADMIN instead of ADMIN in your tests.Whitehurst
I have with no luck, as of springsecurity4 its just ADMINAgglomeration
Possible solution: #32905357Outbreed
L
113

Try use hasAuthority instead hasRole inside HTML-tag.

sec:authorize="hasAuthority('ADMIN')"
Lockridge answered 25/11, 2015 at 14:34 Comment(4)
I had the same issue. hasRole() doesn't seem to work when moving from Spring 3 to Spring 4. Using hasAuthority() worked for me. @Agglomeration can you confirm if it works for you and validate this answer ?Hoffer
I can also confirm this behaviour. No explanation found so far.Localize
There is an explanation #19525880Camshaft
This doesn't work for me - <span sec:authentication="principal.authorities">No Authorities Captured</span> gives [ROLE_USER, ROLE_ADMIN] but <div sec:authorize="hasAuthority(' ROLE_ADMIN')"> This content is only shown to Authority - Admin. </div> doesn't work.Randalrandall
D
36

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

Daph answered 2/1, 2018 at 4:19 Comment(0)
Y
19

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

Yuma answered 29/6, 2016 at 10:33 Comment(0)
B
7

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.

Bushmaster answered 4/9, 2015 at 5:40 Comment(0)
P
6

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!

Perryperryman answered 7/9, 2015 at 6:31 Comment(0)
D
2

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>
Dagoba answered 1/6, 2016 at 22:7 Comment(0)
C
1

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>
Catchy answered 17/6, 2015 at 11:38 Comment(2)
The prefix is not necessary when using security expressions (introduced in Spring Security 3)... it was necessary to name roles with ROLE_ prefix when the access conditions were based on config attributes and AccessDecisionVoter evaluation. The prefix allowed to distinguish between roles and other conditions (e.g. IS_AUTHENTICATED_FULLY).Whiplash
I tried it without the ${ ... } but still not working. My temporal solution to the problem is to use th:if="${#strings.contains(#authentication.principal.authorities,'ADMIN')}"Agglomeration
Y
0

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

Yogurt answered 19/5, 2020 at 16:13 Comment(0)
D
0

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')")
Desirable answered 1/3, 2021 at 5:49 Comment(0)
H
0

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

Hump answered 17/6, 2021 at 22:36 Comment(0)
M
0

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

}
Moran answered 21/7, 2021 at 5:10 Comment(0)
R
0

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 {
}
Reggie answered 16/1, 2022 at 6:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.