Spring 3(+) Security - authorization with roles and rights

Enterprise applications often use an authentication setup that is comprised of roles and rights. Users have a number of roles and roles have a number of rights in such an approach. The advantage of this approach is that a structure of roles and rights offers flexible and fine grained access control to resources.

Below is a database layout that is often used to implement the roles/rights authorization paradigm. In this article I am going to show you

  1. How to configure Spring Security.
  2. How to create your own login form.
  3. How to authenticate users using your own authentication provider and authentication service.
  4. How to do all this using the table layout below, so that we can have flexible and fine grained access control.
Database layout roles and rights

But first, I am going to give you some background information on Spring Security. I myself am completely new to Spring Security, so by all means drop me a line in the comments if I am saying something that doesn't make sense.

Spring Security approaches

I was very surprised to learn that Spring actually does not support the roles/rights security approach. Spring Security has two basic security approaches.

  • Simple, role-based security without rights.
  • Complex ACL based security that defines permissions at the domain object level. 

Most applications need way less than the complex approach, but more than the simple approach that uses just roles and doesn't have a roles to rights mapping like we want to.

Spring's security lingo

The Spring Security documentation uses the terms "permissions", "roles" and "authorities" interchangeably. For newcomers like myself this is pretty confusing, so let me clarify what they all mean.

  • Roles are roles. They always mean the same. Roles are basically strings that can be used to grant or deny access to URLs or methods. By default, roles in Spring must start with ROLE_ so that Spring can distinguish them from other authorities such as "IS_AUTHENTICATED_FULLY" when they are stored together inside a list of GrantedAuthorities.
  • GrantedAuthority is a simple interface that does little more than wrapping a String value that represents a role or other security-related strings like IS_AUTHENTICATED_ANONYMOUSLY . So a GrantedAuthority is really just a generic wrapper for either roles or different authentication strings.
  • Permissions are two things in the Spring Security documentation. First, the word permission is used very often to simply indicate a permission in the general sense of the word. In the context of complex authentication using ACLs a permission is a string representation of the right to perform a specific operation on a domain object. When reading the word "permission" you should be aware of the context. In most cases it is just used in the general sense of the word.

When I started with Spring Security I thought Spring actually did have a role/right structure, because I thought roles were... roles and permissions were rights. That is not the case. In the documentation the word permission is used in the very general sense of the word or when it applies to ACL security and  the hasPermission() method also applies to ACL based security in Spring. In simple authentication, we are only dealing with roles.

Spring's role-only approach is reflected by the Authentication object, which plays a central role :) in Spring Security. Authentication has the getAuthorities() method which returns a collection of GrantedAuthority objects which are just string wrappers that are mostly used to represent role names or other built in authentication strings like IS_AUTHENTICATED_ANONYMOUSLY. 

A security approach using rights instead of roles

Now, Spring security unfortunately actually doesn't support a security implementation that supports both roles and rights. The best we can do is to base our Spring authorization policies on rights instead of roles.

I have investigated the option of creating custom Expression Language methods in order to support both roles and rights, but this is not going to work or it is going to get ugly quickly, because the basic Spring interfaces (like Authentication) just don't support a distinction between roles and rights. They support a list of GrantedAuthority's and that's it. 

What we are going to do is to implement an approach that Wille Wheeler from springinpractice.com already proposed in 2010 and jbbarquero in 2012 at the Spring forums. We will use rights instead of roles and we will draw the rights from the database structure that I showed you earlier.

1. Configuring Spring Security in web.xml

First, we must add a filter to the web.xml. This filter will make Spring look in the application context where we can define our security settings. Add the following filter.

<filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>
            org.springframework.web.filter.DelegatingFilterProxy
        </filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

We are not ready yet in web.xml. We must have Spring load the spring-security.xml file that we are going to create next. Below is an example of how I did this using the context-param.

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:**/application-context.xml,
            classpath*:**/spring-security.xml
        </param-value>
    </context-param>

2. Creating spring-security.xml

Create the spring-security.xml file in a location that corresponds to what you configured in web.xml. In my case, anywhere on the classpath.

<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:security="http://www.springframework.org/schema/security"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <http auto-config="true" use-expressions="true">
        <intercept-url pattern="/manage/*" access="hasRole('ROLE_RIGHT_access_management_screens')" />
        <security:logout logout-success-url="/" invalidate-session="true"/>
        <form-login login-page="/login" default-target-url="/manage/home" />
    </http>

    <global-method-security pre-post-annotations="enabled"/>

    <beans:bean id="produxAuthenticationProvider" class="nl.codebasesoftware.produx.authentication.ProduxAuthenticationProvider"/>

    <security:authentication-manager>
        <security:authentication-provider ref="produxAuthenticationProvider"/>
    </security:authentication-manager>

</beans:beans>

Explanation:

  • The http element is the root of all web related configurations. Note that I set the use-expressions attribute to "true". This enables the use of Spring Expression Language. This is not strictly necessary for our approach, but I enables it anyway, because it gives me more pssibilities that I might use at a later stage in development.
  • The intercept-url element is used to secure individual URLs or URL patterns. In case of my sample application (called "Produx") I wanted to secure all URLs that start with /manage, because this is where the administration area of the application is. Not that I am using an expression in the access attribute. For access to /manage* URLs the role ROLE_RIGHT_access_management_screens is required. (As you can see from the naming of this role, this is where it becomes apparent that Spring doesn't support rights. It is actually a right in my application, but Spring knows only roles and requires the ROLE_ prefix)
  •  The security:logout element allows us to specify a page where the user is routed to after logging out. Nothing fancy there.
     
  • The form-login element should be self-explanatory as well. But for those of you with hangovers or slow minds: it let's us tell Spring where the login form resides (login-page) and where users should be sent after a successful login attempt (default-target-url).
     
  • The global-method-security element is required if we want to secure methods using Spring Security (and we do!) and setting the pre-post-annotations to "true" enables the use of annotations like @PreAuthorize that are relatively new and allow the use of Expression Language.
     
  • The last thing in spring-security.xml is the definition of a custom authentication provider. We will use this to implement our own authentication logic. In my application the custom provider is called ProduxAuthenticationProvider. You might want to make up your own class name and package here. Note that the authentication provider is passed into the configuration of the authentication-manager.

Now, let's see what this authentication provider is all about.

3. Creating an authentication provider

An authentication provider does what its name implies: it provides authentication logic. You can create your own authentication provider by implementing the AuthenticationProvider interface.

@Component(value = "authenticationProvider")
public class ProduxAuthenticationProvider implements AuthenticationProvider {

    private UserProfileService userProfileService;

    @Autowired
    public ProduxAuthenticationProvider(UserProfileService userProfileService) {
        this.userProfileService = userProfileService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UserProfile profile = userProfileService.findByEmail(authentication.getPrincipal().toString());

        if(profile == null){
            throw new UsernameNotFoundException(String.format("Invalid credentials", authentication.getPrincipal()));
        }

        String suppliedPasswordHash = DigestUtils.shaHex(authentication.getCredentials().toString());

        if(!profile.getPasswordHash().equals(suppliedPasswordHash)){
            throw new BadCredentialsException("Invalid credentials");
        }

        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(profile, null, profile.getAuthorities());

        return token;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return aClass.equals(UsernamePasswordAuthenticationToken.class);
    }

Explanation:

  • The UserProfileService is a service that retrieves a UserProfile from the database through JPA/Hibernate. I will get to the UserProfile later. It implements Spring's UserDetails interface, although this is not a requirement. Also, it can provide a list of rights through the getAuthorities() method.
     
  • The authenticate() method is what it is all about. It returns a UsernamePasswordAuthenticationToken if authentication succeeds. This token is an implementation of Spring's Authentication interface. The third argument in its constructor is a list of GrantedAuthority objects. This list is used by Spring to later determine access rights to URLs and methods. The first two constructor arguments can contain anything you like really, because their type is Object, but often the first one is used to contain some sort of "principal", which represents a person or a system. What "principal" really is is not defined by Spring.  I used the UserProfile object as the first constructir parameter.

    Notice how the collection of GrantedAuthority objects in the third parameter of UsernamePasswordAuthentication is obtained from the UserProfile. This is where we are supplying our rights to Spring security. I will discuss the UserProfile entity in the next paragraph.
     
  • If authentication fails the method throws an Authentication exception and Spring will notice this and forward the user to the login-failure-url if that was provided in the form-login element.

4. Creating domain entities like users, rights, roles, etc.

I am not going to deeply into this. It is obvious we need some user entity that has roles that in turn has rights. How you do this in your application is up to you. 

In my application I use Hibernate/JPA to query a Mysql database. The resulting user entity looks like this.

@Entity
public class UserProfile implements DomainObject, UserDetails {

    private Long id;
    private Integer version;
    private String email;
    private String passwordHash;
    private String firstName;
    private String lastName;
    private String phone;
    private Company company;
    private Set<Role> roles;

    @Transient
    private final String PERMISSION_PREFIX = "ROLE_RIGHT_";

    @Override
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public final Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @ManyToMany (fetch = FetchType.EAGER)
    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }

    @Override
    @Transient
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Set<ProduxAuthority> authorities = new HashSet<ProduxAuthority>();
        for (Role role : roles) {
            for (Right right : role.getRights()) {
                ProduxAuthority produxAuthority = new ProduxAuthority(PERMISSION_PREFIX + right.getName());
                authorities.add(produxAuthority);
            }
        }
        return authorities;
    }
    
    // ommitted other getters and setters for brevity
}

The getAuthorities() method returns a collection of GrantedAuthority object.The actual Implementation is ProduxAuthority which shown below. Note that we are returning rights here and that right names are prefixed with ROLE_RIGHT_. Spring requires all authorities to be prefix with ROLE_ and I wanted to indicate that this is still a right, so I chose ROLE_RIGHT_ as the prefix.

 public class ProduxAuthority implements GrantedAuthority {

    private String authority;

    public ProduxAuthority(String authority) {
        this.authority = authority;
    }

    @Override
    public String getAuthority() {
        return authority;
    }

    @Override
    public int hashCode() {
        return authority.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == null) return false;
        if(!(obj instanceof ProduxAuthority)) return false;
        return ((ProduxAuthority) obj).getAuthority().equals(authority);
    }
}

As you can see ProduxAuthority is just a simple string wrapper object.
 

5. Creating the form controller

In spring-security.xml we indicated that the login form can be found at /login

<form-login login-page="/login" default-target-url="/manage/home" />

So, lets bring up a form for this URL, shall we? First we build a simple controller that maps to the /login URL and we call it LoginFormController.

@Controller
@RequestMapping(value = "/login")
public class LoginFormController {

    @Resource(name = "authenticationProvider")
    AuthenticationProvider authenticationProvider;

    private UserProfileService userProfileService;
    private LoginFormValidator validator;

    @Autowired
    public LoginFormController(UserProfileService userProfileService, LoginFormValidator validator) {
        this.userProfileService = userProfileService;
        this.validator = validator;
    }

    @RequestMapping(method = RequestMethod.GET)
    public String createForm(Model model) {
        model.addAttribute("mainContent", "forms/springLogin");
        UserProfile userProfile = new UserProfile();
        model.addAttribute("userProfile", userProfile);
        return "main";
    }

}

The createForm() method creates the form, obviously. You may wonder why there is no method to respond to the login form being submitted. That is because Spring security will take over the form submission and use our custom authentication provider to do the authentication.

Note that I am returning "main" from createForm. This is my main JSP that contains the site skeleton HTML. The "mainContent" attribute that is added to the model will insert the forms/springLogin.jsp form into the mainContent jsp. This is just an example. What matters is that the login form is shown.

6. Creating the login form

The login itself is shown below:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

<form name='f' action="<c:url value='j_spring_security_check' />" method='POST'>
    <div id="data-entry-form">

        <div class="form-entry">
            <label><spring:message code="login.form.label.email"/></label>
            <input type='text' name='j_username' value=''>
        </div>
        <div class="form-entry">
            <label><spring:message code="login.form.label.password"/></label>
            <input type='password' name='j_password'/>
        </div>
        <div class="form-entry">
            <input type="submit" value="Verzenden"/>
        </div>
    </div>
</form>

Note the j_spring_security_check, j_username and j_password names. These names are required or Spring won't understand you want it to handle authentication for you. Note also the <spring:message> elements. I use the for internationalization. Replace them if you don't need these.

7. Test URL security

You're done! All you have to do now is

  • fill the database with users, roles and rights 
  • create an url under /manage* by mapping a controller method to this URL. I use /manage/home
  • visit the /manage/home URL and see what happens.

If all is well you should be redirected to the /login URL automatically. This means your /manage* URL is properly secured.''

8. Test method security

In order to test method security you could create a service method and annotate it with @PreAuthorize for example. I used one of my CompanyServiceImpl methods to test method security.

@Override
    @Transactional(readOnly = true)
    @PreAuthorize("hasRole('ROLE_PERM_access_management_screens')")
    public Company findByUserProfile(UserProfile userProfile) {
        return userProfile.getCompany();
    }

If the logged in user doesn't have the proper role or right in our case, that the server (Jetty, in my case) will return a 403 error.

access denied spring

Conclusion

You are now able to assign rights to roles in the database, which is a flexible approach for access management. We used Spring's role-based approach and filled it with things that are known as 'rights' in our application.

There is one thing missing in this implementation that we unfortunately cannot get. We are using rights instead of roles and not rights and roles and in an ideal world we would want to use both. The actual roles (the Role entity) in this setup don't play any role in access management. They are just containers for rights. If Spring would support the role/right paradigm it might then become possible to use both roles AND rights for access management. We could use something like hasRole('ROLE_ADMIN') to require the ADMIN role for a certain method or URL and we might use something like hasRight('RIGHT_SHOW_USER_STATUS') if we wanted to make a user status URL or method available to users with the SHOW_USER_STATUS right.

 

I hope you learned something. I sure did in the last couple of days. Below are a few posts I found around the web that deal with the same questions I have been dealing with in the past days. And remember, I am new to Spring and Spring security. If you have something to clarify or add, please use the comments.

Related questions and posts on the web

http://slackspace.de/articles/roles-permissions-with-spring-security-3/

http://stackoverflow.com/questions/6357579/spring-security-with-roles-and-permissions

http://grails.1312388.n4.nabble.com/How-to-model-permissions-related-to-a-user-Role-using-Spring-Security-td4399721.html

http://springinpractice.com/2010/10/27/quick-tip-spring-security-role-based-authorization-and-permissions/

http://forum.springsource.org/showthread.php?122398-Permissions-in-roles

Nikola at Friday, January 11, 2013 7:27 PM By far, the best spring security example on the web! Great job!

John at Thursday, May 30, 2013 4:16 AM Nice article. Thank you

Omid at Saturday, October 05, 2013 5:32 PM Thanks for this article, I implemented in my project as you explained, but came to question to make it more flexible. I'm going to use right as combination of controller and action (like ROLE_manage_index), so the question is how can I manage and check all controllers(url) checking centrally, instead of adding intercept urls for every new controller and action ?

Francois at Thursday, October 17, 2013 2:49 PM Hello, thanks for the article, it is really good. Could you please post the rest of your domain classes? I have trouble seeing how you implement those. Thanks, -F.

ray at Tuesday, October 22, 2013 10:02 AM Hi, I wonder how you could design that DB design within LDAP? Because we use LDAP as our source directory control for managing. thanks.

ray at Tuesday, October 22, 2013 10:09 AM Hi, I wonder how you could design that DB design within LDAP? Because we use LDAP as our source directory control for managing. thanks.

ray at Tuesday, October 22, 2013 10:09 AM Hi, I wonder how you could design that DB design within LDAP? Because we use LDAP as our source directory control for managing. thanks.

Sebastien at Sunday, March 02, 2014 12:56 AM Great!! It's exactly what I need. Thanks a lot! :-) It's a good complement to this : http://fruzenshtein.com/spring-mvc-security-mysql-hibernate/

Samuel Chong at Monday, March 03, 2014 3:41 PM Hi, its great... thx but i already have a question... what i save in the atribute name from px_right? is there any source code ? thx

Your email address will not be shown on the site
9 x 2 =