Spring 4 Security - Authorization with Roles and Rights

First of all, spring security doesn't support roles/rights security approach.
It has two basic security approaches:
  • Simple, role-based security without rights.
  • Complex ACL based security that defines permissions at the domain object level. 
I will not go through ACL based security because most applications need a way less than the complex approach.
I will show you how to tweak the simple approach to implement roles/rights security.

1. Configuring spring security filter in web.xml

 <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 must configure spring to load spring-secuirty.xml for security configurations

 <context-param>  
             <param-name>contextConfigLocation</param-name>  
             <param-value>  
                   /WEB-INF/mvc-dispatcher-servlet.xml,  
                   /WEB-INF/spring-dataaccess.xml,  
                   /WEB-INF/spring-security.xml  
             </param-value>  
 </context-param>  

mvc-dispatcher-servlet.xml is the beans file for spring mvc dispatcher servlet and spring-dataaccess.xml  contains datasource configurations. It is simple configuration i won't show here.

2. Create spring-security.xml

 <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"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  
       http://www.springframework.org/schema/security  
       http://www.springframework.org/schema/security/spring-security.xsd">  

       <http auto-config="true" use-expressions="true">  
             <intercept-url pattern="/admin/**" access="hasAuthority('RIGHT_ADMIN_CAN_VIEW')" />  
             <intercept-url pattern="/customer/**" access="hasAuthority('RIGHT_CUSTOMER_CAN_VIEW')" />  
             <form-login   
                   login-page="/login"  
                    default-target-url="/welcome"  
                    authentication-failure-url="/login?error"  
                    username-parameter="username"  
                    password-parameter="password"/>  
                    <logout logout-url="/logout" logout-success-url="/welcome"/>  
       </http>  
       <authentication-manager>  
             <authentication-provider user-service-ref="userService"></authentication-provider>  
       </authentication-manager>  

 </beans:beans>  


  • 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.
  • The intercept-url element is used to secure individual URLs or URL patterns. I wanted to secure all URLs that start with /admin to be accessed only by users who has right "RIGHT_ADMIN_CAN_VIEW" and all URLs that start with /customer to be accessed only by users who has right "RIGHT_CUSTOMER_CAN_VIEW".  As you see i have used hasAuthority not hasRole becuase hasRole expects that the role starts with ROLE_ .
  • The form-login and logout element should be self-explanatory. It tell Spring where the login form resides (login-page), where users should be sent after a successful login attempt (default-target-url) and allows us to specify a page where the user is routed to after logging out. Nothing fancy there.
  • The last thing is the authentication-provider i will use UserDetailsService. I will go through UserService implementation in the next section. 

3. Implement UserService

 import org.springframework.beans.factory.annotation.Autowired;  
 import org.springframework.security.core.userdetails.UserDetails;  
 import org.springframework.security.core.userdetails.UserDetailsService;  
 import org.springframework.security.core.userdetails.UsernameNotFoundException;  
 import net.blogger.model.User;  
 import net.blogger.repository.UserRepository;  

 public class UserServiceImpl  implements UserDetailsService {  

       @Autowired  
       private UserRepository userRepository;  
       public User findByUsername(String username) {  
             return userRepository.findByUsername(username);  
       }  

       @Override  
       public UserDetails loadUserByUsername(String username)  
                   throws UsernameNotFoundException {  
             return findByUsername(username);  
       }  
 }  

UserService should implements UserDetailsService, implement the loadUserByUsername method and return UserDetails object, so our User object should extends UserDetails.


4. Implement User, Role and Right 

 
 import javax.persistence.Column;  
 import javax.persistence.Entity;  
 import javax.persistence.GeneratedValue;  
 import javax.persistence.GenerationType;  
 import javax.persistence.Id;  
 import javax.persistence.Transient;  
 import org.springframework.security.core.GrantedAuthority;  

 @Entity  
 public class Right implements GrantedAuthority {  

       private static final long serialVersionUID = -5241253642226829252L;  

       @Id  
       @Column(name="id")  
       @GeneratedValue(strategy=GenerationType.IDENTITY)  
       private long id;  

       @Column(name="name")  
       private String name;  

       @Column(name="description")  
       private String description;  

       @Override  
       @Transient  
       public String getAuthority() {  
             return name;  
       }  

       public long getId() {  
             return id;  
       }  
       public void setId(long id) {  
             this.id = id;  
       }  
       public String getName() {  
             return name;  
       }  
       public void setName(String name) {  
             this.name = name;  
       }  
       public String getDescription() {  
             return description;  
       }  
       public void setDescription(String description) {  
             this.description = description;  
       }  
 }  

In our example we let spring security to authorize by rights that is why Right should extend GrantedAuthority. below is the Role class, nothing to say except that Role has a set of rights.

 import java.io.Serializable;  
 import java.util.Set;  
 import javax.persistence.Column;  
 import javax.persistence.Entity;  
 import javax.persistence.FetchType;  
 import javax.persistence.GeneratedValue;  
 import javax.persistence.GenerationType;  
 import javax.persistence.Id;  
 import javax.persistence.JoinTable;  
 import javax.persistence.JoinColumn;  
 import javax.persistence.OneToMany;  

 @Entity  
 public class Role implements Serializable{  
       private static final long serialVersionUID = 240818399093543891L;  
       @Id  
       @Column(name="id")  
       @GeneratedValue(strategy=GenerationType.IDENTITY)  
       private long id;  

       @Column(name="name")  
       private String name;  

       @Column(name="description")  
       private String description;  

       @OneToMany(fetch = FetchType.EAGER)  
   @JoinTable(name = "role_permissions",  
     joinColumns    = { @JoinColumn(name = "role_id",    referencedColumnName = "id") },  
     inverseJoinColumns = { @JoinColumn(name = "permission_id", referencedColumnName = "id") }  
   )    
   private Set<Right> rights;  

       public long getId() {  
             return id;  
       }  
       public void setId(long id) {  
             this.id = id;  
       }  
       public String getName() {  
             return name;  
       }  
       public void setName(String name) {  
             this.name = name;  
       }  
       public String getDescription() {  
             return description;  
       }  
       public void setDescription(String description) {  
             this.description = description;  
       }  
       public Set<Right> getRights() {  
             return permissions;  
       }  
       public void setRights(Set<Right> right) {  
             this.rights = rights;  
       }  
 }  

As we said before User must extends UserDetails and override getAuthorities method to return a collection of GrantedAuthority (Rights).
 
 import java.util.Collection;  
 import java.util.HashSet;  
 import java.util.Set;  
 import javax.persistence.Column;  
 import javax.persistence.Entity;  
 import javax.persistence.FetchType;  
 import javax.persistence.GeneratedValue;  
 import javax.persistence.GenerationType;  
 import javax.persistence.Id;  
 import javax.persistence.JoinColumn;  
 import javax.persistence.JoinTable;  
 import javax.persistence.OneToMany;  
 import javax.persistence.Transient;  
 import org.springframework.security.core.GrantedAuthority;  
 import org.springframework.security.core.userdetails.UserDetails;  


 @Entity  
 public class User implements UserDetails{  
       private static final long serialVersionUID = -4393723464870198563L;  

       @Id  
       @GeneratedValue(strategy=GenerationType.IDENTITY)  
       @Column(name="id")  
       private long id;  

       @Column(name="username")  
       private String username;  

       @Column(name="password")  
       private String password;  

       @Column(name="enabled")  
       private boolean enabled;  

       @OneToMany(fetch = FetchType.EAGER)  
       @JoinTable(name = "user_roles",  
       joinColumns    = { @JoinColumn(name = "user_id",    referencedColumnName = "id") },  
       inverseJoinColumns = { @JoinColumn(name = "role_id", referencedColumnName = "id") }  
       )    
       private Set<Role> roles;  

       @Override  
       @Transient  
       public Collection<GrantedAuthority> getAuthorities() {  
              Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();  
              for(Role role:roles){  
                    authorities.addAll(role.getRights());  
              }  
             return authorities;  
       }  

       @Override  
       public boolean isAccountNonExpired() {  
             return true;  
       }  

       @Override  
       public boolean isAccountNonLocked() {  
             return true;  
       }  

       @Override  
       public boolean isCredentialsNonExpired() {  
             return true;  
       }
  
       public long getId() {  
             return id;  
       }  

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

       @Override  
       public String getUsername() {  
             return username;  
       }  

       public void setUsername(String username) {  
             this.username = username;  
       }  

       @Override  
       public String getPassword() {  
             return password;  
       }  

       public void setPassword(String password) {  
             this.password = password;  
       }  

       public boolean isEnabled() {  
             return enabled;  
       }  

       public void setEnabled(boolean enabled) {  
             this.enabled = enabled;  
       }  

       public Set<Role> getRoles() {  
             return roles;  
       }  

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

That is it. Hope it is useful. 

Comments

Popular posts from this blog

Binary tree post order traverse without recursion in Java