Spring Security Advanced Authorization

In this post we implement some more advanced authorization practices. We already showed in previous post how to use Spring Security to setup access control in one page based on user roles. Here we will show how we can extend this authorization with security tags and method security.

This post follows Spring Security Simple Authorization but adds also some more advanced authorization practices. 

Authentication :  We ask the user for his credentials. If they are  correct, he enters to the dashboard. If they are wrong, he gets an error message.

Authorization :  We have 2 user roles, the user and the admin. Both have access to the dashboard and object pages. Only admin has access to admin page. If a simple user tries to access the admin page he will get redirected to the 403 page. Dashboard and object page are accessible only by authenticated users.

On the object page the admin can create and view objects while the user can only view them. The access control is implemented both in jsp’s(sec tag) and in service layer(method security with annotations).

The full github repository can be found here: SpringSecurityAdvancedAuthorization

Technologies Used

  • Spring MVC
  • Spring Security
  • Hibernate
  • MySQL
  • Maven
  • Jsp , Jstl

Database

On this example, we introduce the permissions to our solution, which means that we need some database changes too. First of all, we need to create a table to hold the permissions. After that, we map them with the roles using a many-to-many relationship. One role can have many permissions, and one permission can be assigned to many roles. Also, we create the object table and we map it with the user to store each object’s creator. The rest tables (user and role) will remain the same.

CREATE TABLE role
(
  role_id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(200) NOT NULL,
  UNIQUE (name)
);

create table permission
(
  permission_id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  UNIQUE (name)
);


create table role_permission
(
  role_id BIGINT,
  permission_id BIGINT,
  FOREIGN KEY (role_id) REFERENCES role(role_id),
  FOREIGN KEY (permission_id) REFERENCES permission(permission_id)
);


CREATE TABLE user
(
  user_id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT ,
  username VARCHAR(400) NOT NULL,
  password VARCHAR(400) NOT NULL,
  role_id BIGINT,
  FOREIGN KEY (role_id) REFERENCES role(role_id)
);

CREATE TABLE object
(
  object_id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT ,
  name VARCHAR(400) NOT NULL,
  value INT,
  creator_id BIGINT  NOT NULL,
  FOREIGN KEY (creator_id) REFERENCES user(user_id)
);



INSERT INTO role (name) VALUE("USER");
INSERT INTO role (name) VALUE("ADMIN");


insert into permission (name) values("VIEW_OBJECT");
insert into permission (name) values("CREATE_OBJECT");
insert into permission (name) values("EDIT_OBJECT");
insert into permission (name) values("DELETE_OBJECT");
insert into permission (name) values("VIEW_ADMIN_PAGE");


insert into role_permission VALUES(1,1);
insert into role_permission VALUES(1,3);
insert into role_permission VALUES(2,1);
insert into role_permission VALUES(2,2);
insert into role_permission VALUES(2,3);
insert into role_permission VALUES(2,4);
insert into role_permission VALUES(2,5);

INSERT INTO user (username, password, role_id) VALUES ("testUser", "$2a$10$04TVADrR6/SPLBjsK0N30.Jf5fNjBugSACeGv1S69dZALR7lSov0y",1);
INSERT INTO user (username, password, role_id) VALUES ("testAdmin", "$2a$10$04TVADrR6/SPLBjsK0N30.Jf5fNjBugSACeGv1S69dZALR7lSov0y",2);

 

Implementation

On the security configuration file we will just add the new page.

spring-security.xml

<http auto-config="true" use-expressions="true">

    <intercept-url pattern="/dashboard**" access="isAuthenticated()" />
    <intercept-url pattern="/object**" access="isAuthenticated()" />
    <intercept-url pattern="/admin**" access="hasAuthority('VIEW_ADMIN_PAGE')" />

    <access-denied-handler error-page="/403" />

    <form-login
        login-page="/login"
        default-target-url="/dashboard"
        authentication-failure-url="/login?error"
        username-parameter="username"
        password-parameter="password" />

    <logout logout-success-url="/login?logout" />

</http>

 

In the previous posts we examined page level security. Now we will understand how we can secure actions that are included in the same page. There are many ways to do that and one of them is using the spring security  tags. In the next example the users who have the ‘view object’ authority can see the object table, but only those who have the ‘create object’ authority can create one.

object.jsp

<sec:authorize access="hasAuthority('VIEW_OBJECT')">

    <table class="table table-bordered table-striped">

        <c:forEach items="${objects}" var="object">
            <tr>
                <td>${object.name}</td>
                <td>${object.value}</td>
            </tr>
        </c:forEach>

    </table>

</sec:authorize>


<sec:authorize access="hasAuthority('CREATE_OBJECT')">

    <form:form  action ="create-object" method='POST' commandName="objectForm">

        <h2>Create object</h2>

        <div class="form-group">
            <label for="nameInput">Object Name</label>
            <form:input id="nameInput" path="name" placeholder="Object Name" size="30" cssClass="form-control" />
        </div>

        <div class="form-group">
            <label for="valueInput">Value</label>
            <form:input id="valueInput" path="value" placeholder="Value" size="30" cssClass="form-control" />
        </div>

        <button class="btn btn-default" type="submit">Create Object</button>

    </form:form>

</sec:authorize>

 

In this example the users don’t have only roles but also authorities and the relationship between them is one-to-many. This means that we need also to change our user details service implementation, in order to assign to a user all his authorities.

CustomUserDetailsService.java

@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserDao userDao;

    /**Method that returns a UserDetails object
     *
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Transactional(readOnly = true)
    @Override
    public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {

        //get user from the database, via Hibernate
        com.antogeo.entity.User user = userDao.getUserByUsername(username);

        //Collect User authorities
        List<GrantedAuthority> authorities = buildUserAuthority(user);

        //Build Spring Security User
        User springSecurityUser = buildUserForAuthentication(user, authorities);
        return springSecurityUser;
    }


    /**Build the Spring Security User.
     *
     * @param user
     * @param authorities
     * @return
     */
    private User buildUserForAuthentication(com.antogeo.entity.User user, List<GrantedAuthority> authorities) {

        return new User(user.getUsername(), user.getPassword(), true, true, true, true, authorities);
    }


    private List<GrantedAuthority> buildUserAuthority(com.antogeo.entity.User user) {

        Set<GrantedAuthority> authoritiesSet = new HashSet<GrantedAuthority>();

        //Get user permissions from role
        Set<Permission> permissions = user.getRole().getPermissions();

        // Build user's authorities
        for (Permission permission : permissions) {
            authoritiesSet.add(new SimpleGrantedAuthority(permission.getName()));
        }

        //Transform the Set into List
        List<GrantedAuthority> result = new ArrayList<GrantedAuthority>(authoritiesSet);
        return result;
    }



    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

 

So far we have assigned to the users multiple authorities and manage their authorization on the pages and actions based on them. In the next step, we will show how to perform security in the Service layer also. We create another service named ObjectService and we apply method security on the insertObject() method. Using the preAuthorize tag, only the user with the ‘create object’ authority can enter that method. This adds an extra security layer to our application in case someone bypass the view layer.

Having now two Service classes, we create an abstract one to be extended by the others. This class contains the shared functionality like the insert and get functions.

AbstractService.java

public abstract class AbstractService<T> {

    public abstract List<T> getAll();

    public abstract T insert(Object o);

    public abstract List<T> getByUserId(long userId);

}

 

UserService.java

@Service("userService")
@EnableTransactionManagement
public class UserService extends AbstractService {

    @Autowired
    private UserDao userDao;

    @Override
    public List getAll() {
        return null;
    }

    @Override
    public Object insert(Object o) {
        return null;
    }

    @Override
    public List getByUserId(long userId) {
        return null;
    }


    @Transactional(readOnly = true)
    public User getUserByUsername(String username) {

        return userDao.getUserByUsername(username);
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

 

ObjectService.java

@Service("objectService")
@EnableTransactionManagement
public class ObjectService extends AbstractService {

    @Autowired
    private ObjectDao objectDao;

    @Autowired
    private UserService userService;

    @Transactional(readOnly = true)
    @Override
    public List<Object> getAll() {

        return objectDao.getAll();
    }

    @Transactional(readOnly = false)
    @Override
    public Object insert(java.lang.Object o) {

        Object object = objectDao.insert(o);

        return object;
    }


    @Transactional(readOnly = true)
    @Override
    public List<com.antogeo.entity.Object> getByUserId(long userId) {

        return objectDao.getByUserId(userId);
    }

    @PreAuthorize("hasRole('CREATE_OBJECT')")
    @Transactional(readOnly = false)
    public Object insertObject(ObjectForm form, Principal principal) {

        User user = userService.getUserByUsername(principal.getName());

        Object object = new Object(form.getName(), form.getValue(), user);

        object = insert(object);

        return object;
    }


    public void setObjectDao(ObjectDao objectDao) {
        this.objectDao = objectDao;
    }
}

 

So to sum up, in this post we extended our existing authorization in two sections. We added permission control in the View layer for each action separately, and method security in the Service layer. Using the different tags we can now configure the permission management in great detail.

 

Installation and Run

  • Run the database/queries.sql  queries to your database.
  • Add your database credentials to resources/db.properties file.
  • Build the project using mvn clean install
  • Run the application using tomcat

Test Credentials

Username Password Role
testUser 123456 user
testAdmin 123456 admin