User-Role-Permission security pattern (RBAC) in Spring Security 4

A common access control pattern in enterprise applications is role-based access control (RBAC). Here's how to do it in Spring Security 4 using a custom UserDetailsService.


For the scope of this article I'm assuming a PostgreSQL database.

    actorId serial PRIMARY KEY,
    login character varying(255) UNIQUE NOT NULL,
    password character varying(255) NOT NULL,
    enabled boolean NOT NULL,
    firstName character varying(255),
    lastName character varying(255),
    email character varying(255) NOT NULL,
    created timestamp,
    lastLogin timestamp

    roleId serial PRIMARY KEY,
    name character varying(255) UNIQUE NOT NULL,
    displayName character varying(255),
    description character varying(255)

CREATE TABLE Permission  
    permissionId serial PRIMARY KEY,
    name character varying(255) UNIQUE NOT NULL,
    description character varying(255)

    actorRoleId serial PRIMARY KEY,
    actorId integer REFERENCES Actor,
    roleId integer REFERENCES Role

CREATE TABLE RolePermission  
    rolePermissionId serial PRIMARY KEY,
    roleId integer REFERENCES Role,
    permissionId integer REFERENCES Permission

The custom UserDetails

Expand this class with additional attributes of your Actor entity (e.g. first name, last name, email, address, ...)

public class CustomUserDetails implements UserDetails {

    private static final long serialVersionUID = 9188230014174856593L;

    private String password;
    private final String username;
    private final Set<GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;

    public CustomUserDetails (String username, String password,
            Collection<? extends GrantedAuthority> authorities) {
        this(username, password, true, true, true, true, authorities);

    public CustomUserDetails (String username, String password,
            boolean enabled, boolean accountNonExpired,
            boolean credentialsNonExpired, boolean accountNonLocked,
            Collection<? extends GrantedAuthority> authorities) {

        if (((username == null) || "".equals(username)) || (password == null)) {
            throw new IllegalArgumentException(
                    "Cannot pass null or empty values to constructor");

        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.authorities = Collections

    private static SortedSet<GrantedAuthority> sortAuthorities(
            Collection<? extends GrantedAuthority> authorities) {
                "Cannot pass a null GrantedAuthority collection");
        // Ensure array iteration order is predictable (as per
        // UserDetails.getAuthorities() contract and SEC-717)
        SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<GrantedAuthority>(
                new AuthorityComparator());

        for (GrantedAuthority grantedAuthority : authorities) {
                    "GrantedAuthority list cannot contain any null elements");

        return sortedAuthorities;

    private static class AuthorityComparator implements
            Comparator<GrantedAuthority>, Serializable {
        private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

        public int compare(GrantedAuthority g1, GrantedAuthority g2) {
            // Neither should ever be null as each entry is checked before
            // adding it to
            // the set.
            // If the authority is null, it is a custom authority and should
            // precede
            // others.
            if (g2.getAuthority() == null) {
                return -1;

            if (g1.getAuthority() == null) {
                return 1;

            return g1.getAuthority().compareTo(g2.getAuthority());

    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;

    public String getPassword() {
        return password;

    public String getUsername() {
        return username;

    public boolean isAccountNonExpired() {
        return accountNonExpired;

    public boolean isAccountNonLocked() {
        return accountNonLocked;

    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;

    public boolean isEnabled() {
        return enabled;

The custom UserDetailsService

Adapt the loadUserByUsername method to feed the additional custom actor entity attributes to the CustomUserDetails object.

@Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {

    private ActorRepository actorRepository;

    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {

        try {
            Actor actor = actorRepository.findByLogin(username);

            boolean enabled = true;
            boolean accountNonExpired = true;
            boolean credentialsNonExpired = true;
            boolean accountNonLocked = true;

            // adapt as needed
            return new CustomUserDetails(actor.getLogin(),
                    actor.getPassword(), enabled, accountNonExpired,
                    credentialsNonExpired, accountNonLocked,

        } catch (Exception e) {
            throw new RuntimeException(e);

    private static class SimpleGrantedAuthorityComparator implements
            Comparator<SimpleGrantedAuthority> {

        public int compare(SimpleGrantedAuthority o1, SimpleGrantedAuthority o2) {
            return o1.equals(o2) ? 0 : -1;

     * Retrieves a collection of {@link GrantedAuthority} based on a list of
     * roles
     * @param roles
     *            the assigned roles of the user
     * @return a collection of {@link GrantedAuthority}
    public Collection<? extends GrantedAuthority> getAuthorities(Set<Role> roles) {

        Set<SimpleGrantedAuthority> authList = new TreeSet<SimpleGrantedAuthority>(
                new SimpleGrantedAuthorityComparator());

        for (Role role : roles) {

        return authList;

     * Wraps a {@link Role} role to {@link SimpleGrantedAuthority} objects
     * @param roles
     *            {@link String} of roles
     * @return list of granted authorities
    public static Set<SimpleGrantedAuthority> getGrantedAuthorities(Role role) {

        Set<SimpleGrantedAuthority> authorities = new HashSet<SimpleGrantedAuthority>();

        Set<Permission> rolePermissions = role.getPermissions();
        for (Permission permission : rolePermissions) {
            authorities.add(new SimpleGrantedAuthority(permission.getName()));

        return authorities;

The Spring configuration

In this example I'm using XML to configure Spring Security. Naturally this can also be done using annotations.

<beans:beans xmlns=""  
    xmlns:beans="" xmlns:xsi=""

    <http auto-config="true" authentication-manager-ref="adminAuthMgr">
        <intercept-url pattern="/admin/**"
            access="hasAuthority('PERM_ACCESS_ADMIN_AREA')" />

        <form-login login-page="/login" default-target-url="/admin/dashboard"
            username-parameter="username" password-parameter="password"
            login-processing-url="/j_spring_security_check" />
        <logout logout-url="/j_spring_security_logout"
            logout-success-url="/login?logout" />
        <csrf />

    <authentication-manager alias="adminAuthMgr">
            <password-encoder hash="bcrypt" />


Note how you can use the hasAuthority(PERM_XY) or hasAuthority(ROLE_ZY) expression to elegantly handle that both permissions from the Permission table and roles from the Role table are stored in the permissions attribute of Spring UserDetails.


Read more posts by this author.

Dortmund, Germany

Subscribe to Programming & Whisky

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!