We have thought interesting to split apart this section, since users and roles are usually borderline between the application and Spring Security.
In this recipe, we will install the Spring Security dependencies and update the User
Entity. We will also create an Authority
entity that is based on a custom Role
enum that we created. Finally, we update the init.sql
script to add a set of existing users.
v5.x.x
. Then, run a maven clean install
command on the cloudstreetmarket-parent
module (right-click on the module, go to Run as… | Maven Clean, and then navigate to Run as… | Maven Install). Execute a Maven Update Project
to synchronize Eclipse with the maven configuration (right-click on the module and then navigate to Maven | Update Project…).cloudstreetmarket-parent
, cloudstreetmarket-core
and cloudstreetmarket-api
::<!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.0.0.RELEASE</version> </dependency>
User
entity has been updated. It now reflects the users
table (instead of the previous user
table). It also implements the UserDetails
interface:@Entity @Table(name="users") public class User implements UserDetails{ private static final long serialVersionUID = 1990856213905768044L; @Id @Column(name = "user_name", nullable = false) private String username; @Column(name = "full_name") private String fullName; private String email; private String password; private boolean enabled = true; private String profileImg; @Column(name="not_expired") private boolean accountNonExpired; @Column(name="not_locked") private boolean accountNonLocked; @Enumerated(EnumType.STRING) private SupportedCurrency currency; @OneToMany(mappedBy= "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @OrderBy("id desc") private Set<Action> actions = new LinkedHashSet<Action>(); @OneToMany(mappedBy="user", cascade = CascadeType.ALL, fetch = FetchType.EAGER) private Set<Authority> authorities = new LinkedHashSet<Authority>(); @OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY) private Set<SocialUser> socialUsers = new LinkedHashSet<SocialUser>(); //getters and setters as per the UserDetails interface ... }
This User
Entity has a relationship with SocialUser
. SocialUser
comes into play with the OAuth2 authentication, and we will develop this part later.
authorities
table. This Entity also implements the GrantedAuthority
interface. The class is the following:@Entity @Table(name"="authorities"", uniqueConstraints={@UniqueConstraint(columnNames = "{"username""","authority""})}) public class Authority implements GrantedAuthority{ private static final long serialVersionUID = 1990856213905768044L; public Authority() {} public Authority(User user, Role authority) { this.user = user; this.authority = authority; } @Id @GeneratedValue private Long id; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = ""username"", nullable=false) private User user; @Column(nullable = false) @Enumerated(EnumType.STRING) private Role authority; //getters and setters as per the GrantedAuthority //interface ... }
Role
Enum in the cloudstreetmarket-core
module, for the different roles:public enum Role { ROLE_ANONYMOUS, ROLE_BASIC, ROLE_OAUTH2, ROLE_ADMIN, ROLE_SYSTEM, IS_AUTHENTICATED_REMEMBERED; //Transitory role }
init.sql
file. The existing pre-initialization scripts related to users, have been adapted to suit the new schema:insert into users(username, fullname, email, password, profileImg, enabled, not_expired, not_locked) values ('userC', '', '[email protected]', '123456', '', true, true, true); insert into authorities(username, authority) values ('userC', 'ROLE_'BASIC');
username: <marcus> email: <[email protected]> password: <123456> preferred currency: <USD>
Then, click on the user icon in order to upload a profile picture.
Finally, hit the Sign up button and the popup should disappear.
http://cloudstreetmarket.com/api/users/marcus
. The application should fetch the following persisted data for the Marcus user:The recipe at this stage preconfigures our entities so they comply with Spring Security. A couple of concepts about Spring Security are mentioned in this part and developed in the following sections.
Spring Security is built around three core components: the SecurityContextHolder
object, the SecurityContext
, and the Authentication
object.
The SecurityContextHolder
object allows us to define and carry for one JVM a SecurityContextHolderStrategy
implementation (focused on storing and retrieving a SecurityContext
).
By default, and in most of the designs, the selected-strategy uses Threadlocals
(ThreadLocalSecurityContextHolderStrategy
).
A Tomcat instance manages a Spring MVC servlet (like any other servlet) with multiple threads as the multiple HTTP requests come in. The code is as follows:
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>(); ... }
Each thread allocated to a request on Spring MVC has access to a copy of the SecurityContext
carrying an Authentication
object for one user (or one other identifiable thing).
Once a copy of the SecurityContext
is no longer referred, it gets garbage-collected.
There is a bunch of noticeable interfaces in Spring Security. We will particularly visit Authentication
, UserDetails
, UserDetailsManager
, and GrantedAuthority
.
The Spring
Authentication
object can be retrieved from the SecurityContext
. This object is usually managed by Spring Security but applications still often need to access it for their business.
Here is the interface for the Authentication
object:
public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }
It provides access to the Principal
(representing the identified user, entity, company or customer), its credentials, its authorities and to some extra-details that may be needed. Now let's see how, from the SecurityContextHolder
, a user can be retrieved:
Object principal = SecurityContextHolder.getContext() .getAuthentication() .getPrincipal(); if (principal instanceof UserDetails) { String username = ((UserDetails) principal).getUsername(); } else { String username = principal.toString(); }
The Principal
class can be cast into the Spring UserDetails
Type, which is exposed by the core framework. This interface is used as a standard bridge in several extension-modules (Spring Social, Connect, Spring Security SAML, Spring Security LDAP, and so on.).
The UserDetails
implementations represent a Principal in an extensible and application-specific way.
You must be aware of the one-method UserDetailsService
interface that provides the key-method loadUserByUsername
for account-retrieval within the core framework:
public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
Spring Security offers two implementations for this interface: CachingUserDetailsService
and JdbcDaoImpl
, whether we want to benefit from an in-memory UserDetailsService
or from a JDBC-based UserDetailsService
implementation. More globally, what usually matters is where and how users and roles are persisted so Spring Security can access this data on itself and process authentications.
The way Spring Security accesses the user and role data is configured with the selection or the reference of an authentication-provider in the Spring Security configuration file with the security namespace.
Here are two examples of configuration when using the native UserDetailsService
implementations:
<security:authentication-manager alias="authenticationManager"> <security:authentication-provider> <security:jdbc-user-service data-source-ref="dataSource" /> </security:authentication-provider> </security:authentication-manager>
This first example specifies a JDBC-based UserDetailsService
. The next example specifies an in-memory UserDetailsService.
<security:authentication-manager alias="authenticationManager"> <security:authentication-provider> <security:user-service id="inMemoryUserDetailService"/> </security:authentication-provider> </security:authentication-manager>
In our case, we have registered our own UserDetailsService
implementation (communityServiceImpl)
as follows:
<security:authentication-manager alias="authenticationManager"> <security:authentication-provider user-service-ref='communityServiceImpl'> <security:password-encoder ref="passwordEncoder"/> </security:authentication-provider> </security:authentication-manager>
We thought more appropriate to continue accessing the database layer through the JPA abstraction.
Spring Security provides a UserDetails
implementation org.sfw.security.core.userdetails.User
, which can be used directly or extended. The User class is defined as follows:
public class User implements UserDetails, CredentialsContainer { 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; ... }
Guiding us towards a structure for UserDetails
, Spring Security also provides a UserDetailsManager
interface for managing users:
public interface UserDetailsManager extends UserDetailsService { void createUser(UserDetails user); void updateUser(UserDetails user); void deleteUser(String username); void changePassword(String oldPassword, String newPassword); boolean userExists(String username); }
Spring Security has two native implementations for non-persistent (InMemoryUserDetailsManager
) and JDBC-based (JdbcUserDetailsManager
) user-managements.
When deciding not to use a built-in authentication-provider, it is a good practice to implement the presented interfaces, especially for guaranteeing backward compatibility on the coming versions of Spring Security.
Within Spring Security, GrantedAuthorities
reflects the application-wide permissions granted to a Principal
. Spring Security guides us towards a role-based authentication. This kind of authentication imposes the creation of groups of users able to perform operations.
Roles can be pulled out of the Authentication
object from getAuthorities()
, as an array of GrantedAuthority
implementations.
The GrantedAuthority
interface is quite simple:
public interface GrantedAuthority extends Serializable { String getAuthority(); }
The GrantedAuthority
implementations are wrappers carrying a textual representation for a role. These textual representations are potentially matched against the configuration attributes of secure objects (we will detail this concept in the Authorizing on services and controllers recipe).
The Role
embedded in a GrantedAuthority
, which is accessed from the getAuthority()
getter, is more important to Spring Security than the wrapper itself.
We have created our own implementation: the Authority
that entity that has an association to User
. The framework also provides the SimpleGrantedAuthority
implementation.
In the last recipe, we will talk about the Spring Security authorization process. We will see that Spring Security provides an AccessDecisionManager
interface and several AccessDecisionManager
implementations. These implementations are based on voting and use
AccessDecisionVoter
implementations. The most commonly used of these implementations is the RoleVoter
class.
The Spring Security authentication and authorization process will be covered in depth in the Authorizing on services and controllers recipe. This section introduces more details from the Spring Security reference document.
The Spring Securitysecurity reference is an amazing source of theoretical and practical information.
The technical overview is a great introduction to the Spring Security Framework:
http://docs.spring.io/spring-security/site/docs/3.0.x/reference/technical-overview.html
The Spring Security reference provides many Spring Security examples on different authentications types (LDAP, OPENID, JAAS, and so on.). Other role-based examples can also be found at:
http://docs.spring.io/spring-security/site/docs/3.1.5.RELEASE/reference/sample-apps.html
Find out more about the built-in UserDetailsService
implementations (in-memory or JDBC) at:
http://docs.spring.io/spring-security/site/docs/3.1.5.RELEASE/reference/core-services.html