Adapting users and roles to Spring Security

We have thought interesting to split apart this section, since users and roles are usually borderline between the application and Spring Security.

Getting ready

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.

How to do it...

  1. From the Git Perspective in Eclipse, checkout the latest version of the branch 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…).

    Note

    You will notice a few changes both in the frontend and backend of the code.

  2. Spring Security comes with the following dependencies, added in 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>
  3. The 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.

  4. An Authority Entity has been created and maps a 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
      ...
    }
  5. For a more readable code, we have created a 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
    }
  6. Also, we have made a few changes in the 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');
  7. Start the application. (No exceptions should be observed).
  8. Click on the login button (on the right-hand side of the main menu). You will see the following popup that allows entering a username and a password to log in:
    How to do it...
  9. You also have the option to create a new user. In the previous popup, click on the Create new account link that can be found at the bottom right. This will load the following pop-up content:
    How to do it...
  10. Let's create a new user with the following values:
    username: <marcus>
    email: <[email protected]>
    password: <123456>
    preferred currency: <USD>

    Note

    For the profile picture, you must create on your file system, the directory structure corresponding to the property pictures.user.path in cloudstreetmarket-api/src/main/resources/application.properties.

    Then, click on the user icon in order to upload a profile picture.

    How to do it...

    Finally, hit the Sign up button and the popup should disappear.

  11. Now, call the following URI: http://cloudstreetmarket.com/api/users/marcus. The application should fetch the following persisted data for the Marcus user:
    How to do it...

How it works...

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.

Introduction to Spring Security

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).

Note

The SecurityContextHolder has the following static field:

private static SecurityContextHolderStrategy strategy;

By default, and in most of the designs, the selected-strategy uses Threadlocals (ThreadLocalSecurityContextHolderStrategy).

ThreadLocal context holders

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.

Noticeable Spring Security interfaces

There is a bunch of noticeable interfaces in Spring Security. We will particularly visit Authentication, UserDetails, UserDetailsManager, and GrantedAuthority.

The Authentication interface

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 interface

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.

Authentication providers

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.

The UserDetailsManager interface

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;
  ...
}

Note

Managing users (create, update, and so on) can be a shared responsibility for Spring Security. It is usually mainly performed by the application though.

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.

The GrantedAuthority interface

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.

Note

Unless there is a strong business meaning for a feature, do prefer for example ROLE_ADMIN or ROLE_GUEST to ROLE_DASHBOARD or ROLE_PAYMENT

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.

Note

The RoleVoter implementation votes positively for the user authorization when a configuration attribute (the textual representation of an Authority) starts with a predefined prefix. By default, this prefix is set to ROLE_.

There is more…

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.

Spring Security reference

The Spring Securitysecurity reference is an amazing source of theoretical and practical information.

Technical overview

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

Sample applications

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

Core services

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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset