Difference between Role and GrantedAuthority in Spring Security
Think of a GrantedAuthority as being a "permission" or a "right". Those "permissions" are (normally) expressed as strings (with the getAuthority()
method). Those strings let you identify the permissions and let your voters decide if they grant access to something.
You can grant different GrantedAuthoritys (permissions) to users by putting them into the security context. You normally do that by implementing your own UserDetailsService that returns a UserDetails implementation that returns the needed GrantedAuthorities.
Roles (as they are used in many examples) are just "permissions" with a naming convention that says that a role is a GrantedAuthority that starts with the prefix ROLE_
. There's nothing more. A role is just a GrantedAuthority - a "permission" - a "right". You see a lot of places in spring security where the role with its ROLE_
prefix is handled specially as e.g. in the RoleVoter, where the ROLE_
prefix is used as a default. This allows you to provide the role names withtout the ROLE_
prefix. Prior to Spring security 4, this special handling of "roles" has not been followed very consistently and authorities and roles were often treated the same (as you e.g. can see in the implementation of the hasAuthority()
method in SecurityExpressionRoot - which simply calls hasRole()
). With Spring Security 4, the treatment of roles is more consistent and code that deals with "roles" (like the RoleVoter
, the hasRole
expression etc.) always adds the ROLE_
prefix for you. So hasAuthority('ROLE_ADMIN')
means the the same as hasRole('ADMIN')
because the ROLE_
prefix gets added automatically. See the spring security 3 to 4 migration guide for futher information.
But still: a role is just an authority with a special ROLE_
prefix. So in Spring security 3 @PreAuthorize("hasRole('ROLE_XYZ')")
is the same as @PreAuthorize("hasAuthority('ROLE_XYZ')")
and in Spring security 4 @PreAuthorize("hasRole('XYZ')")
is the same as @PreAuthorize("hasAuthority('ROLE_XYZ')")
.
Regarding your use case:
Users have roles and roles can perform certain operations.
You could end up in GrantedAuthorities
for the roles a user belongs to and the operations a role can perform. The GrantedAuthorities
for the roles have the prefix ROLE_
and the operations have the prefix OP_
. An example for operation authorities could be OP_DELETE_ACCOUNT
, OP_CREATE_USER
, OP_RUN_BATCH_JOB
etc. Roles can be ROLE_ADMIN
, ROLE_USER
, ROLE_OWNER
etc.
You could end up having your entities implement GrantedAuthority
like in this (pseudo-code) example:
@Entity
class Role implements GrantedAuthority {
@Id
private String id;
@ManyToMany
private final List<Operation> allowedOperations = new ArrayList<>();
@Override
public String getAuthority() {
return id;
}
public Collection<GrantedAuthority> getAllowedOperations() {
return allowedOperations;
}
}
@Entity
class User {
@Id
private String id;
@ManyToMany
private final List<Role> roles = new ArrayList<>();
public Collection<Role> getRoles() {
return roles;
}
}
@Entity
class Operation implements GrantedAuthority {
@Id
private String id;
@Override
public String getAuthority() {
return id;
}
}
The ids of the roles and operations you create in your database would be the GrantedAuthority representation, e.g. ROLE_ADMIN
, OP_DELETE_ACCOUNT
etc. When a user is authenticated, make sure that all GrantedAuthorities of all its roles and the corresponding operations are returned from the UserDetails.getAuthorities() method.
Example:
The admin role with id ROLE_ADMIN
has the operations OP_DELETE_ACCOUNT
, OP_READ_ACCOUNT
, OP_RUN_BATCH_JOB
assigned to it.
The user role with id ROLE_USER
has the operation OP_READ_ACCOUNT
.
If an admin logs in the resulting security context will have the GrantedAuthorities:ROLE_ADMIN
, OP_DELETE_ACCOUNT
, OP_READ_ACCOUNT
, OP_RUN_BATCH_JOB
If a user logs it, it will have:ROLE_USER
, OP_READ_ACCOUNT
The UserDetailsService would take care to collect all roles and all operations of those roles and make them available by the method getAuthorities() in the returned UserDetails instance.
java spring : how can I separate user roles and authorities?
In the end I chose this method :
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : this.roles) {
insert_auth(authorities, "ROLE_" + role.getName());
for (Privilege pri : role.getPrivileges()) {
insert_auth(authorities, pri.getName());
}
}
return authorities;
}
The big advantage it has is that it is called at each endpoint request making it dynamic ( I can add / remove roles and privileges without the user having to logout / login for it to take effect )
This also allows me to scale relatively easily as it can be controlled from an interface and / or by script
Should i use Spring security role base authorisation OR angular Route Gards OR both?
Spring security Role based authorization and Angular Guards are not related.
The Guards in Angular are used to allow the User to see certain parts of the Front End application. Just that.
A Role based security it's used to allow the User to call certain endpoints if he does have certain roles.
Now, should you use both of them? Sure. Would be enough just to implement Spring Security? Sure, but the front end application will not result user friendly.
spring boot security- adding custom role names
for the tutorial/lesson, it is ok when you (try to) rename/refactor:
- the roles (useres, admins ... roles or authorities? tomayto, tomahto (just prepend/cutoff a
ROLE_
;) - the db columns.
But the least intrusive and quite efficient (for only 2 roles/few combinations) approach would be like:
// adjust to requirements:
static final String REGEX_USERS = "student"; // exact match
static final String REGEX_ADMINS = "(teacher|principal)"; // group OR match
static final String AUTH_ADMINS = "ADMINS";
static final String AUTH_USERS = "USERS";
...and then:
this.authorities = Arrays.stream(
user
.getTheType()
.replaceAll(REGEX_USERS, USERS)
.replaceAll(REGEX_ADMINS, ADMINS)
.split(",")
)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
A standalone test:
package com.example.demo;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
class TestO {
static final String REGEX_USERS = "student";
static final String REGEX_ADMINS = "(teacher|principal)";
static final String AUTH_ADMINS = "ADMINS";
static final String AUTH_USERS = "USERS";
public static void main(String[] args) {
String testData1 = "student";
String testData2 = "teacher,principal";
List<GrantedAuthority> result1 = Arrays.stream(testData1
.replaceAll(REGEX_USERS, AUTH_USERS)
.replaceAll(REGEX_ADMINS, AUTH_ADMINS)
.split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
System.out.format("%s%n", result1);
List<GrantedAuthority> result2 = Arrays.stream(testData2
.replaceAll(REGEX_USERS, AUTH_USERS)
.replaceAll(REGEX_ADMINS, AUTH_ADMINS)
.split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
System.out.format("%s%n", result2);
}
}
Prints:
[USERS]
[ADMINS, ADMINS]
If my assumptions(!) about role-mappings were correct:
- all
student
areUSERS
- all
teacher
areADMINS
- there is only 1 (few)
principal
..and alsoADMIN
(and alsoteacher
?? ...please! school systems vary widely...;-) principal
is the only one who has a comma in his (authority) list!?- (no
student
isteacher
!?)
Then probably (and in any "granted authority specific" case):
private java.util.Set<GrantedAuthority> authorities;
...then also:
Collectors.toSet() // + refacotrings
is preferrable! (What is the difference between Set and List? !;)
So:
Set<GrantedAuthority> result2 = Arrays.stream(testData2
.replaceAll(REGEX_USERS, "USERS")
.replaceAll(REGEX_ADMINS, "ADMINS")
.split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
System.out.format("%s%n", result2);
Prints:
...
[ADMINS]
See also (reg. efficient string replacement):
Java Replacing multiple different substring in a string at once (or in the most efficient way)
Spring Boot / Spring Security role based authorization not working properly
I think that's the most unobvious thing about Spring Security. Roles and authorities are the same things but roles should be prefixed with ROLE_
. So, the correct usage is
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
Related Topics
Java8: Ambiguity with Lambdas and Overloaded Methods
Using Java Map for Range Searches
Recursively List All Files Within a Directory Using Nio.File.Directorystream;
Cannot Change Version of Project Facet Dynamic Web Module to 3.0
Arrayindexoutofboundsexception When Iterating Through All the Elements of an Array
Stack with Find-Min/Find-Max More Efficient Than O(N)
Java Regex for Support Unicode
Java.Util.Zip - Recreating Directory Structure
How to Convert a 1D Array to 2D Array
Rotating Bufferedimage Instances
Best Way to Build a Plugin System with Java
Java Ternary Without Assignment
How to Make a Jbutton in a Jtable Cell Click-Able
Java Regular Expressions and Dollar Sign
Why Do == Comparisons with Integer.Valueof(String) Give Different Results for 127 and 128
Copy All Values from Fields in One Class to Another Through Reflection