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
Post a Comment