Spring Boot + Spring Security + H2 Database

In this tutorial , we will see how to secure your web application by authenticating and authorizing users against database. User and role information is stored in database .

GitHub Link:Code for this example can be found on below gitHub link-

Tools :

  • Spring Boot 2.1.1.RELEASE
  • Spring MVC
  • Spring Security
  • Maven
  • IntelliJ IDEA
  • Lombok

Step 1:  Create a simple spring boot application in Intellij IDEA editor as shown in below link –

Our final Project structure will look like –

Step 2:Define all dependencies required in pom file as below –

<?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>
<pre><code><parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.myjavablog</groupId>
<artifactId>spring-boot-database-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-database-security</name>
<description>Demo project for Spring Boot</description>

<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.apache.tomcat</groupId>
                <artifactId>tomcat-jdbc</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <!--<scope>runtime</scope>-->
    </dependency>

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
    </dependency>

</dependencies>

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

In addition to spring-boot-starter-security dependency ,we will be using lombok third party library to reduce boilerplate code for model/data objects, e.g., it can generate getters and setters for those object automatically by using Lombok annotations. The easiest way is to use the @Data annotation.

Step3: Now create a configuration for creating beans for BCryptPasswordEncoder and H2 database . Also we need to configure security mechanism through database using configuration file –

package com.myjavablog.config;

import org.h2.server.web.WebServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class BeanConfiguration implements WebMvcConfigurer {
<pre><code>@Bean
public BCryptPasswordEncoder passwordEncoder() {
    BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    return bCryptPasswordEncoder;
}

@Bean
ServletRegistrationBean h2servletRegistration() {
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(new WebServlet());
    registrationBean.addUrlMappings("/console/*");
    return registrationBean;
}</code></pre>
}

package com.myjavablog.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
<pre><code>@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;

@Autowired
private DataSource dataSource;

@Value("${spring.queries.users-query}")
private String usersQuery;

@Value("${spring.queries.roles-query}")
private String rolesQuery;

@Override
protected void configure(AuthenticationManagerBuilder auth)
        throws Exception {
    auth.
            jdbcAuthentication()
            .usersByUsernameQuery(usersQuery)
            .authoritiesByUsernameQuery(rolesQuery)
            .dataSource(dataSource)
            .passwordEncoder(bCryptPasswordEncoder);
}

@Override
protected void configure(HttpSecurity http) throws Exception {

    http.
            authorizeRequests()
            .antMatchers("/").permitAll()
            .antMatchers("/login").permitAll()
            .antMatchers("/registration").permitAll()
            .antMatchers("/admin/**").hasAuthority("ADMIN").anyRequest()
            .authenticated().and().csrf().disable().formLogin()
            .loginPage("/login").failureUrl("/login?error=true")
            .defaultSuccessUrl("/admin/adminHome")
            .usernameParameter("email")
            .passwordParameter("password")
            .and().logout()
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            .logoutSuccessUrl("/").and().exceptionHandling()
            .accessDeniedPage("/access-denied")

    ;
}

@Override
public void configure(WebSecurity web) throws Exception {

    web.ignoring()
            .antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**", "/console/**");
}</code></pre>
}

Step 4: Create a model classes for User and Role

As we are using lombok libarary , we dont need to create setter and getter in model class . Also it helps in building mock objects at runtime for unit testing.

package com.myjavablog.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import java.util.Set;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "USER")
public class User {
<pre><code>@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "USER_ID")
private int id;
@Column(name = "EMAIL")
@Email(message = "*Please provide a valid Email")
@NotEmpty(message = "*Please provide an email")
private String email;
@Column(name = "PASSWORD")
@Length(min = 5, message = "*Your password must have at least 5 characters")
@NotEmpty(message = "*Please provide your password")
private String password;
@Column(name = "NAME")
@NotEmpty(message = "*Please provide your name")
private String name;
@Column(name = "LAST_NAME")
@NotEmpty(message = "*Please provide your last name")
private String lastName;
@Column(name = "ACTIVE")
private int active;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "USER_ROLE", joinColumns = @JoinColumn(name = "USER_ID"), inverseJoinColumns = @JoinColumn(name = "ROLE_ID"))
private Set<Role> roles;</code></pre>
}

package com.myjavablog.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "ROLE")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ROLE_ID")
private int id;
@Column(name = "ROLE")
private String role;
}

Step 5: Create Service layer –

package com.myjavablog.service;

import com.myjavablog.model.User;
import org.springframework.stereotype.Service;

public interface UserService {
<pre><code>public User findUserByEmail(String email) ;
public User saveUser(User user);</code></pre>
}

package com.myjavablog.service;

import com.myjavablog.model.Role;
import com.myjavablog.model.User;
import com.myjavablog.repository.RoleRepository;
import com.myjavablog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.HashSet;

@Service
public class UserServiceImpl implements UserService {
<pre><code>private UserRepository userRepository;
private RoleRepository roleRepository;
private BCryptPasswordEncoder bCryptPasswordEncoder;

@Autowired
public UserServiceImpl(UserRepository userRepository, RoleRepository roleRepository, BCryptPasswordEncoder bCryptPasswordEncoder) {
    this.userRepository = userRepository;
    this.roleRepository = roleRepository;
    this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}

@Override
public User findUserByEmail(String email) {
    return userRepository.findByEmail(email);
}

@Override
public User saveUser(User user) {
    user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
    user.setActive(1);
    Role userRole = roleRepository.findByRole("ADMIN");
    user.setRoles(new HashSet<Role>(Arrays.asList(userRole)));
    return userRepository.save(user);
}</code></pre>
}

Step 6: Create JPA repositories for User and Role to interact with the databse –

package com.myjavablog.repository;

import com.myjavablog.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface RoleRepository extends JpaRepository<Role, Integer> {
public Role findByRole(String role);
}

package com.myjavablog.repository;

import com.myjavablog.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
public User findByEmail(String email);
}

Step 7: Create a controller to save the user and navigate

package com.myjavablog.controller;

import com.myjavablog.model.User;
import com.myjavablog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import javax.validation.Valid;

@Controller
public class UserController {
<pre><code>@Autowired
private UserService userService;

@RequestMapping(value={"/", "/login"}, method = RequestMethod.GET)
public ModelAndView login(){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("login");
    return modelAndView;
}

@RequestMapping(value="/registration", method = RequestMethod.GET)
public ModelAndView registration(){
    ModelAndView modelAndView = new ModelAndView();
    User user = new User();
    modelAndView.addObject("user", user);
    modelAndView.setViewName("registration");
    return modelAndView;
}

@RequestMapping(value = "/registration", method = RequestMethod.POST)
public ModelAndView createNewUser(@Valid User user, BindingResult bindingResult) {
    ModelAndView modelAndView = new ModelAndView();
    User userExists = userService.findUserByEmail(user.getEmail());
    if (userExists != null) {
        bindingResult
                .rejectValue("email", "error.user",
                        "There is already a user registered with the email provided");
    }
    if (bindingResult.hasErrors()) {
        modelAndView.setViewName("registration");
    } else {
        userService.saveUser(user);
        modelAndView.addObject("successMessage", "User has been registered successfully");
        modelAndView.addObject("user", new User());
        modelAndView.setViewName("registration");

    }
    return modelAndView;
}

@RequestMapping(value="/admin/adminHome", method = RequestMethod.GET)
public ModelAndView home(){
    ModelAndView modelAndView = new ModelAndView();
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    User user = userService.findUserByEmail(auth.getName());
    modelAndView.addObject("userName", "Welcome " + user.getName() + " " + user.getLastName() + " (" + user.getEmail() + ")");
    modelAndView.addObject("adminMessage","This Page is available to Users with Admin Role");
    modelAndView.setViewName("admin/adminHome");
    return modelAndView;
}

@RequestMapping(value="/user/userHome", method = RequestMethod.GET)
public ModelAndView user(){
    ModelAndView modelAndView = new ModelAndView();
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    User user = userService.findUserByEmail(auth.getName());
    modelAndView.addObject("userName", "Welcome " + user.getName() + " " + user.getLastName() + " (" + user.getEmail() + ")");
    modelAndView.addObject("userMessage","This Page is available to Users with User Role");
    modelAndView.setViewName("user/userHome");
    return modelAndView;
}</code></pre>
}

Step 8: Create a UI layer using Thymleaf templates .

Step 9: Run the application and go to H2 database console by navigating to http://localhost:8081/console

Once the application is up and running , H2 database will be automatically started. You can navigate to http://localhost:8081/console to see the tables created as shown below-

Also you need to insert few roles initially.

INSERT INTO ROLE VALUES (1, "ADMIN");
INSERT INTO ROLE VALUES (2, "USER");

Step 10: Now go to application http://localhost:8081/

You can navigate to registration page by clicking on “Go to registration page ” at the top .

Once you register , User details will be saved to USER table –

Now when you login with registered user details then you can see the admin page . As registered user is ADMIN role, Admin page will be displayed.

Leave a Comment

Bitnami