The User. The Role represents the high-level roles of the user in the system. Each role will have a set of low-level privileges. The Privilege represents a low-level, granular privilege/authority in the system.
In RBAC, a role is similar to a user we saw above. Multiple users can have the same role and a given role can be assigned to multiple users. Users are granted access based on the assigned role.
I'm the author of the article in question.
No doubt there are multiple ways to do it, but the way I typically do it is to implement a custom UserDetails
that knows about roles and permissions. Role
and Permission
are just custom classes that you write. (Nothing fancy--Role
has a name and a set of Permission
instances, and Permission
has a name.) Then the getAuthorities()
returns GrantedAuthority
objects that look like this:
PERM_CREATE_POST
, PERM_UPDATE_POST
, PERM_READ_POST
instead of returning things like
ROLE_USER
, ROLE_MODERATOR
The roles are still available if your UserDetails
implementation has a getRoles()
method. (I recommend having one.)
Ideally you assign roles to the user and the associated permissions are filled in automatically. This would involve having a custom UserDetailsService
that knows how to perform that mapping, and all it has to do is source the mapping from the database. (See the article for the schema.)
Then you can define your authorization rules in terms of permissions instead of roles.
Hope that helps.
To implement that, it seems that you have to:
org.springframework.security.authentication.ProviderManager
and configure it (set its providers) to a custom org.springframework.security.authentication.AuthenticationProvider
.
This last one should return on its authenticate method a Authentication, which should be setted with the org.springframework.security.core.GrantedAuthority
, in your case, all the permissions for the given user.The trick in that article is to have roles assigned to users, but, to set the permissions for those roles in the Authentication.authorities
object.
For that I advise you to read the API, and see if you can extend some basic ProviderManager and AuthenticationProvider instead of implementing everything. I've done that with org.springframework.security.ldap.authentication.LdapAuthenticationProvider
setting a custom LdapAuthoritiesPopulator, that would retrieve the correct roles for the user.
Hope this time I got what you are looking for. Good luck.
The basic steps are:
Use a custom authentication provider
<bean id="myAuthenticationProvider" class="myProviderImplementation" scope="singleton">
...
</bean>
Make your custom provider return a custom UserDetails
implementation. This UserDetailsImpl
will have a getAuthorities()
like this:
public Collection<GrantedAuthority> getAuthorities() {
List<GrantedAuthority> permissions = new ArrayList<GrantedAuthority>();
for (GrantedAuthority role: roles) {
permissions.addAll(getPermissionsIncludedInRole(role));
}
return permissions;
}
Of course from here you could apply a lot of optimizations/customizations for your specific requirements.
This is the simplest way to do it. Allows for group authorities, as well as user authorities.
-- Postgres syntax
create table users (
user_id serial primary key,
enabled boolean not null default true,
password text not null,
username citext not null unique
);
create index on users (username);
create table groups (
group_id serial primary key,
name citext not null unique
);
create table authorities (
authority_id serial primary key,
authority citext not null unique
);
create table user_authorities (
user_id int references users,
authority_id int references authorities,
primary key (user_id, authority_id)
);
create table group_users (
group_id int references groups,
user_id int referenecs users,
primary key (group_id, user_id)
);
create table group_authorities (
group_id int references groups,
authority_id int references authorities,
primary key (group_id, authority_id)
);
Then in META-INF/applicationContext-security.xml
<beans:bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="passwordEncoder" />
<authentication-manager>
<authentication-provider>
<jdbc-user-service
data-source-ref="dataSource"
users-by-username-query="select username, password, enabled from users where username=?"
authorities-by-username-query="select users.username, authorities.authority from users join user_authorities using(user_id) join authorities using(authority_id) where users.username=?"
group-authorities-by-username-query="select groups.id, groups.name, authorities.authority from users join group_users using(user_id) join groups using(group_id) join group_authorities using(group_id) join authorities using(authority_id) where users.username=?"
/>
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>
Just for sake of completeness (maybe someone else won't have to implement it from scratch):
We've implemented our own small library, as everyone else. It's supposed to make things easier, so that our developers don't have to reimplement it each time. It would be great if spring security would provide support of rbac out of the box, as this approach is much better than the default permission based one.
Have a look at Github (OSS, MIT license) to see if it suits your needs. It's basically only addressing the role <-> privileges mapping. The missing piece, you'll have to provide on your own is basically the user <-> roles mapping, e.g. by mapping groups (racf/ad groups) to roles (1:1) or by implementing an additional mapping. That one's different in each project, so it doesn't make sense to provide some implementation.
We've basically used this internally, so that we can start with rbac from the beginning. We can still replace it with some other implementation later on, if the application is growing, but it's important for us to get the setup right at the beginning.
If you don't use rbac, there's a good chance, that the permissions are scattered throughout the codebase and you'll have a hard time to extract/group those (into roles) later on. The generated graphs do also help to reason about it/restructure it later on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With