Spring Security hasRole() not working
JavaSpringSpring MvcSpring SecurityThymeleafJava 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 User
has a Set of Role
s
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 yourADMIN Enum
must beROLE_ADMIN
instead ofADMIN
. - If you use
hasAuthority('ADMIN')
, yourADMIN Enum
must beADMIN
.
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:
-
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).
-
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'
-
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 {
}