Spring Security 4 for Spring MVC using Spring Data JPA and Spring Boot




I have been writing a series of tutorials on using Spring Security 4 in Spring MVC application starting from the basic in-memory authentication. In this post, I am writing a step by step guide to secure a Spring MVC application using Spring Security 4 along with Spring Data JPA and Spring Boot. That sounds like a lot of different concepts to learn, but it is really simple. 

If you do not know or if you are new to Spring Data JPA or even JPA (Java Persistence API), you might probably think why should you go for Spring Data JPA when you can simply use Spring JDBC to secure the application with the user details stored in a database. To understand this, read the next paragraph.

Java Persistence API (JPA) - Unlike writing a plain DAO that consists of plain JDBC code everywhere full of PreparedStatements and SqlConnections etc, we just map the original fields in the database table to Java classes called Entities, provide SQL queries and let the persistence api handle the connections, query execution etc without writing much boilerplate code. JPA is just a specification and to use it, you must use a provider of this Specification such as Hibernate. In other words, consider JPA as a set of interfaces and you need an implementation of these interfaces to actually use it, which is called a 'Provider'. Any provider or implementation of JPA specification, lets you create Java classes called Entities, provide SQL queries and handle the connections, query execution etc.

Spring Data JPA takes a step forward and handles the DAO layer around data repositories with out of the box query generation for most commonly required scenarios. It is not an implementation or a provider of JPA, but it sits on top of JPA and uses the provider of your choice, to generate queries and to do all that the JPA specification is meant to do. It provides a stronger design which is also easy for anyone to implement and you are not tied to any JPA provider. For instance if you are using Hibernate directly in your project, and if it has a bug, your development will halt. But if you use Spring Data JPA and use Hibernate as the provider for Spring Data JPA, you can switch anytime to any other provider like EclipseLink or ObjectDB etc with very minimal level of code change.

I am also going to use Spring Boot which takes care of,

-- Setting up and initializing Spring MVC (spring-boot-starter-web)
-- Setting up and initializing Spring Security (spring-boot-starter-security)
-- Setting up and initializing Spring Data JPA with Hibernate as provider (spring-boot-starter-data-jpa)

Note: Spring Boot Starter JPA uses Hibernate by default and it configures everything that is needed to use Spring Data JPA along with Hibernate. You only need to write your entity classes and data repositories. If you want to use any other provider other than Hibernate you should not use Spring Boot Starter JPA, instead include required jars in classpath and write configuration classes to let Spring Data JPA know which provider you are using.

Enough of theory now, lets do some real experiment.




1. Create user database and set up user and roles tables with some data. I am using mysql server.

use userbase;
DROP TABLE IF EXISTS user_roles;
DROP TABLE IF EXISTS users;
CREATE  TABLE users (
  userid int(11) NOT NULL AUTO_INCREMENT,
  username VARCHAR(45) NOT NULL,
  email VARCHAR(255) NOT NULL,
  password VARCHAR(60) NOT NULL ,
  enabled TINYINT NOT NULL DEFAULT 1 ,
  PRIMARY KEY (userid));
  
CREATE TABLE user_roles (
  user_role_id int(11) NOT NULL AUTO_INCREMENT,
  userid int(11) NOT NULL,
  role varchar(45) NOT NULL,
  PRIMARY KEY (user_role_id),
  UNIQUE KEY uni_userid_role (role,userid),
  KEY fk_user_idx (userid),
  CONSTRAINT fk_userid FOREIGN KEY (userid) REFERENCES users (userid));

INSERT INTO users(username,email,password,enabled)
VALUES ('priya','abc@abc.com','$2a$04$CO93CT2ObgMiSnMAWwoBkeFObJlMYi/wzzOnPlsTP44r7qVq0Jln2', true);
INSERT INTO users(username,email,password,enabled)
VALUES ('naveen','def@def.com','$2a$04$j3JpPUp6CTAe.kMWmdRNC.Wie58xDNPfcYz0DBJxWkucJ6ekJuiJm', true);

INSERT INTO user_roles (userid, role)
VALUES (001, 'ROLE_USER');
INSERT INTO user_roles (userid, role)
VALUES (002, 'ROLE_ADMIN');
INSERT INTO user_roles (userid, role)
VALUES (002, 'ROLE_USER');

2. Next create a maven project and replace the 'pom.xml' file with the below code.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.programmingfree</groupId>
    <artifactId>SpringSecuritySpringDataJpaApp</artifactId>
    <version>0.1.0</version>
 <packaging>war</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.2.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <scope>provided</scope>
  </dependency>
  <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
  </dependency>
  <!-- tag::web[] -->
  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
  <!-- end::web[] -->
  <!-- tag:: Spring Data JPA -->
   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- end:: Spring Data JPA -->
        <!-- tag::security[] -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- end::security[] -->       

        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

We have included all dependencies that is required to set up Spring Security using Spring Data JPA, in the pom.xml.

3. Create application.properties file at the same location as that of the pom.xml file.

application.properties

# Replace with your connection string
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/userbase
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.database-platform = org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql = true

# Hibernate
spring.jpa.hibernate.ddl-auto=update

4. Create entity and repository classes.

User.java

package domain;

import java.io.Serializable;
import javax.persistence.*;

@Entity
@Table(name = "users")
public class User implements Serializable {

 private static final long serialVersionUID = 1L;
 
 @Id
    @GeneratedValue(strategy = GenerationType.AUTO)    
    @Column(name="userid")
    private Long userId;

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

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

 @Column(name = "email")
    private String email;
    
 @Column(name ="enabled")
 private int enabled;
 
 public User(){
  
 }
 
 public User(User user) {
         this.userId = user.userId;
         this.userName = user.userName;
         this.email = user.email;       
         this.password = user.password;
         this.enabled=user.enabled;        
 }
 
 public int getEnabled() {
  return enabled;
 }

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

 public Long getUserid() {
  return userId;
 }

 public void setUserid(Long userid) {
  this.userId = userid;
 }
 
 public String getPassword() {
  return password;
 }

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

 public void setEmail(String email) {
  this.email = email;
 }

 public String getUserName() {
  return userName;
 }

 public void setUserName(String userName) {
  this.userName = userName;
 }
}

UserRole.java

package domain;

import javax.persistence.*;

@Entity
@Table(name="user_roles")
public class UserRole {
 
 @Id
    @GeneratedValue(strategy = GenerationType.AUTO)    
    @Column(name="user_role_id")
 private Long userroleid;
 
 @Column(name="userid")
 private Long userid;
 
 @Column(name="role")
 private String role; 

 public String getRole() {
  return role;
 }

 public void setRole(String role) {
  this.role = role;
 }

 public Long getUserid() {
  return userid;
 }

 public void setUserid(Long userid) {
  this.userid = userid;
 }

 public Long getUserroleid() {
  return userroleid;
 }

 public void setUserroleid(Long userroleid) {
  this.userroleid = userroleid;
 } 
 
}

User class is annotated with @Entity, indicating that it is a JPA entity. We also have @Table annotation, with the table name to which this entity will be mapped to. In case the table name and the name of entity class is the same you can omit @Table annotation.

User class has five attributes, the id, the username, the password, the email and the enabled.

The userid property is annotated with @Id so that JPA will recognize it as the object’s ID. The id property is also annotated with @GeneratedValue to indicate that the ID should be generated automatically.

You can see the properties are annotated with @Column annotation. This is not required if the columnname in the table is the same as that of the property name in this class.

We also have two constructors. The default constructor only exists for the sake of JPA. You won’t use it directly. The other constructor is the one you’ll use to create instances of User to be used by spring security to authenticate.

We also have another entity class for user_roles table.

Spring security requires data from users and user_roles table to authenticate and authorize an user. So we need classes that retrieves data from these two tables and this is done by defining repositories in Spring Data JPA. In our application, we need two repository interfaces defined, the UserRepository and the UserRolesRepository. Spring Data JPA has the ability to create repository implementations automatically, at runtime, from a repository interface. Traditionally we used to write DAO classes with sql connections queries etc, but here everything is done by Spring Data JPA.

UserRepository.java

package domain;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends CrudRepository<User, Long> {
    public User findByUserName(String username);
    
}

UserRolesRepository.java

package domain;
import java.util.List;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRolesRepository extends CrudRepository<UserRole, Long> {
 
    @Query("select a.role from UserRole a, User b where b.userName=?1 and a.userid=b.userId")
    public List<String> findRoleByUserName(String username);
 
}

We have defined an UserRepository interface that extends the CrudRepository interface. The type of entity and ID that it works with,User and Long, are specified in the generic parameters on CrudRepository. By extending CrudRepository, UserRepository inherits several methods for working with User persistence, including methods for saving, deleting, and finding User entities.

Spring Data JPA also allows you to define other query methods by simply declaring their method signature. In the case of UserRepository, we have defined findByUserName() method, which takes username as a parameter and returns all the matching rows based on the username parameter. You dont even have to write an implementation of this method, but just with this definition,Spring Data JPA creates an implementation on the fly when you run the application.

In case there is a requirement to write our own queries we can do that too. In this application we have used userid as foreign key for the roles table. So in UserRolesRepository I have written my own query on the findRoleByUserName method just by adding @Query annotation to this method. Please note that the query is written using JPA Query Language with the entity names for tables and property names for column names. There are several other ways to write custom queries, you can learn about it later from spring documentation if you are interested.

4. Now that we are done with repositories, next step is to create classes required for spring security to use the data returned by the user repositories to authenticate users. Spring Security looks for an implementation of UserDetailsService interface that loads user specific data. So we have to write a class that implements UserDetailsService, and override the one method that this interface has, loadbyusername.

CustomUserDetailsService.java

package security;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

import domain.User;
import domain.UserRepository;
import domain.UserRolesRepository;

@Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
 private final UserRepository userRepository;
 private final UserRolesRepository userRolesRepository;
 
 @Autowired
    public CustomUserDetailsService(UserRepository userRepository,UserRolesRepository userRolesRepository) {
        this.userRepository = userRepository;
        this.userRolesRepository=userRolesRepository;
    }
 
        
 @Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  User user=userRepository.findByUserName(username);
  if(null == user){
   throw new UsernameNotFoundException("No user present with username: "+username);
  }else{
   List<String> userRoles=userRolesRepository.findRoleByUserName(username);
   return new CustomUserDetails(user,userRoles);
  }
 }
  
}

CustomUserDetailsService class that implements UserDetailsService overrides loadUserByUsername method. This method uses userRepository.findByUserName method to get the User details specific to the provided username. We also use the  userrolesrepository to get a list of roles associated with the user.

Now there is one more thing to learn here. We may have lot of information inside our user table that we may need to access in our application. But Spring security does not require all the information that are in the user table but only a few properties to perform authentication and authorization. So in order to return only the required details to spring security, we write a class that implements UserDetail interface.

CustomUserDetails.java

package security;

import java.util.Collection;
import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import domain.User;

public class CustomUserDetails extends domain.User implements UserDetails { 
 
 private static final long serialVersionUID = 1L;
 private List<String> userRoles;
 

 public CustomUserDetails(User user,List<String> userRoles){
  super(user);
  this.userRoles=userRoles;
 }
 
 
 @Override
 public Collection<? extends GrantedAuthority> getAuthorities() {
  
  String roles=StringUtils.collectionToCommaDelimitedString(userRoles);   
  return AuthorityUtils.commaSeparatedStringToAuthorityList(roles);
 }

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


 @Override
 public String getUsername() {
  return super.getUserName();
 }


}

UserDetail interface provides the core user information. CustomUserDetails class that implements UserDetails interface, has methods to return a set of authorities, username , password and few other methods. It already extends the User, so it is a User and it also have some extra methods that the Spring security requires.

5. Create the front end application to display user information and to facilitate login. Create the following JSP files under webapp/WEB-INF-jsp folder.

home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<title>Spring Security Example - ProgrammingFree</title>
</head>
<body>
  <h1>Welcome!</h1>  
  Click <a href="<spring:url value='/hello' />">here</a> to see a
  greeting   
</body>
</html>

hello.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<title>Greeting Page - ProgrammingFree</title>
</head>
<body> 
 <h1>
  Hello <b><c:out value="${pageContext.request.remoteUser}"></c:out></b>
 </h1>
 <form action="/logout" method="post">
  <input type="submit" class="button red big" value="Sign Out" /> <input
   type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
 </form> 
</body>
</html>

login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<title>Login Page - ProgrammingFree</title>
</head>
<body> 
 <form action="/login" method="post">  
  <div>
   <input type="text" name="username"
    placeholder="User Name" />
  </div>
  <div>
   <input type="password" name="password"
    placeholder="Password" />
  </div>
  <div>
   <input type="submit" value="Sign In" class="button red small" />
  </div>
  <c:if test="${param.error ne null}">
   <div class="alert-danger">Invalid username and password.</div>
  </c:if>
  <c:if test="${param.logout ne null}">
   <div class="alert-normal">You have been logged out.</div>
  </c:if>  
  <input type="hidden" name="${_csrf.parameterName}"
   value="${_csrf.token}" />
 </form>

</body>
</html>

403.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Access Denied Page - ProgrammingFree</title>
</head>
<body>   
 <div class="alert-danger">
  <h3>You do not have permission to access this page!</h3> 
 </div>
 <form action="/logout" method="post">
  <input type="submit" class="button red big" value="Sign in as different user" /> <input
   type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
 </form>   
</body>
</html>

The idea is to have an unprotected welcome page (home.jsp) that presents a link to the greeting page (hello.jsp) that is protected by spring security. Once the user clicks on the link in the welcome page, he/she will be redirected to login page and once authenticated, the greeting page will be presented to the user. To demonstrate authorization, we will add security configuration to display greeting page for users with ADMIN role only.

5. Final step is to write all required configuration classes including the security configuration class.

WebSecurityConfig.java

package config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import security.CustomUserDetailsService;

@Configuration
@EnableWebMvcSecurity
@ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

 @Autowired 
 private UserDetailsService userDetailsService;
 
 @Autowired
 public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {    
  auth.userDetailsService(userDetailsService).passwordEncoder(passwordencoder());
 } 
 

 
 @Override
 protected void configure(HttpSecurity http) throws Exception {
   http.authorizeRequests()
  .antMatchers("/hello").access("hasRole('ROLE_ADMIN')")
  .anyRequest().permitAll()
  .and()
    .formLogin().loginPage("/login")
    .usernameParameter("username").passwordParameter("password")
  .and()
    .logout().logoutSuccessUrl("/login?logout") 
   .and()
   .exceptionHandling().accessDeniedPage("/403")
  .and()
    .csrf();
 }
 
 @Bean(name="passwordEncoder")
    public PasswordEncoder passwordencoder(){
     return new BCryptPasswordEncoder();
    }
}


We have set up our custom UserDetailsService class already and now we have passed it to the AuthenticationManagerBuilder in the web security configuration. HttpSecurity is configured to define the pages to be authenticated and authorized, specify the login page name, specify access denied page etc. For anyone who tries to access the /hello will be redirected to login page and those users with ADMIN role is only presented with the page.

I have created a password encoder bean that uses Spring's BCryptPasswordEncoder. In the database I have stored bcrypt encoded passwords and not plain texts, so this password encoder tells spring security to decrypt the password based on bcrypt algorithm and then process it. If you notice, I have placed hidden inputs to set csrf tokens in all the protected JSP files. This is one way of implementing CSRF protection in Spring Security. You can read about other ways of implementing it here.

MvcConfig.java

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter{

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/403").setViewName("403");
    }    
    
    @Bean
 public InternalResourceViewResolver viewResolver() {
  InternalResourceViewResolver resolver = new InternalResourceViewResolver();
  resolver.setPrefix("/WEB-INF/jsp/");
  resolver.setSuffix(".jsp");
  return resolver;
 }    
}


The web application is based on Spring MVC. To expose the JSP pages, we need to set up view controllers. This is done by overriding the addviewcontrollers method in the WebMvcConfigurerAdapter class.

Application.java

package config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.orm.jpa.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@EnableJpaRepositories(basePackages = "domain")
@EntityScan(basePackages = "domain")
public class Application {

    public static void main(String[] args) throws Throwable {
        SpringApplication.run(Application.class, args);
    }

Finally, we have the Application class to launch this application. All the annotations used on top of the class declaration are essential for Spring to identify all JPA related and security related configuration.

That is all on the implementation side. When the user tries access a protected page,

-- Login page is presented with a form that captures username and password and posts it to "/login"
-- As configured, Spring Security provides a filter that intercepts that request ("/login")and authenticates the user.
-- If the user fails to authenticate, the page is redirected to "/login?error" and we display the appropriate error message in the login form
-- Upon successfully signing out, our application is sent to "/login?logout" and the page displays the appropriate success message.

To run the application, download it from above link, import it as Maven Project in eclipse
and run as -> Maven Build -> Goals -> clean install spring-boot:run


Make sure you setup mysql database for user details as explained above and make necessary property changes in the application.properties file to match with your database settings.








Keep yourself subscribed for getting programmingfree articles delivered directly to your inbox once in a month. Thanks for reading!


Subscribe to GET LATEST ARTICLES!


Related

Trending 2597672379557997206

Post a Comment

  1. How can some one write such a beautiful and easy to understand tutorial on spring security :)

    Appreciated your efforts.

    Thanks

    ReplyDelete
    Replies
    1. Takes time and effort but possible :)

      Delete
  2. How can some one write such a beautiful and easy to understand tutorial on spring security :)

    Appreciated your efforts.

    Thanks

    ReplyDelete
  3. Hi, how can I add the registration to this example? Can you please make a mini guide for this?

    ReplyDelete
  4. This tutorial is tremendously helpful, thank you for your effort.

    ReplyDelete
  5. Best tutorial i have ever seen for spring security with spring boot.
    Kudos to the developer. Great work.! "CHEERS"

    ReplyDelete
  6. what's the password for users: priya and naveen

    ReplyDelete
  7. Its: priya/priya & naveen/naveen

    ReplyDelete
  8. Extremely useful guide. Thanks a lot, the best I've found about sprint security

    ReplyDelete
  9. Hi Priya
    I am importing your code using IntelliJ, but im running some errors. Seating with this problem for almost a week. Please advised what am i missing here?

    ReplyDelete
  10. how to automatically go to hello page after sucessfull login?

    ReplyDelete
  11. Please make a video for registration
    Thank you

    ReplyDelete
  12. Please provide tutorial for jsf with spring security.

    ReplyDelete

emo-but-icon

SUBSCRIBE


item