Spring Boot Web Application, Part 6 – Spring Security with DAO Authentication Provider

Spring Boot Web Application, Part 6 – Spring Security with DAO Authentication Provider

48 Comments

Last Updated on October 21, 2024 by jt

This is part 6 of the tutorial series for building a web application using Spring Boot. In this post, we look at adding a DAO Authentication provider for Spring Security.

We started off with the first part by creating our Spring project using the Spring Initializr. In part 2, we rendered a web page using Thymeleaf and Spring MVC. This was followed by part 3 where we looked at setting up Spring Data JPA for database persistence. Part 4 was all about consolidating everything to provide a working Spring Boot MVC Web Application capable of performing CRUD operations.

In the previous part 5 of this series, we configured a basic in-memory authentication provider. It’s a good starting point to learn Spring Security, but as I mentioned there, it’s not for enterprise applications. A production-quality implementation would likely use the DAO authentication provider.

In this part of the series, I will discuss Spring Security with the DAO authentication provider to secure our Spring Boot Web application. We will implement both authentication and role-based authorization with credentials stored in the H2 database. For persistence, we will use the Spring Data JPA implementation of the repository pattern, that I covered in part 3. Although there are several Spring Data JPA implementations, Hibernate is by far the most popular.

As the Spring Data JPA dependency is included in our Maven POM, Hibernate gets pulled in and configured with sensible default properties via Spring Boot.

This post builds upon 5 previous posts. If you’re not familiar with all the content around Spring, I suggest you to go through this series from the start.

JPA Entities

Our application already has a Product JPA entity. We’ll add two more entities, User and Role. Following the SOLID design principle’sprogram to interface ” principle, we will start by writing an interface followed with an abstract class for our entities.

DomainObject.java

package guru.springframework.domain;

/**
 * Created by jt on 11/14/15.
 */
public interface DomainObject {

    Integer getId();

    void setId(Integer id);
}

AbstractDomainClass.java

package guru.springframework.domain;

import javax.persistence.*;
import java.util.Date;

/**
 * Created by jt on 12/16/15.
 */
@MappedSuperclass
public class AbstractDomainClass implements DomainObject {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Integer id;

    @Version
    private Integer version;

    private Date dateCreated;
    private Date lastUpdated;

    @Override
    public Integer getId() {
        return this.id;
    }

    @Override
    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public Date getDateCreated() {
        return dateCreated;
    }

    public Date getLastUpdated() {
        return lastUpdated;
    }

    @PreUpdate
    @PrePersist
    public void updateTimeStamps() {
        lastUpdated = new Date();
        if (dateCreated==null) {
            dateCreated = new Date();
        }
    }
}

The entity classes are as follows.

User.java

package guru.springframework.domain;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by jt on 12/14/15.
 */
@Entity
public class User extends AbstractDomainClass  {

    private String username;

    @Transient
    private String password;

    private String encryptedPassword;
    private Boolean enabled = true;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable
    // ~ defaults to @JoinTable(name = "USER_ROLE", joinColumns = @JoinColumn(name = "user_id"),
    //     inverseJoinColumns = @joinColumn(name = "role_id"))
    private List<Role> roles = new ArrayList<>();
    private Integer failedLoginAttempts = 0;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

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

    public String getEncryptedPassword() {
        return encryptedPassword;
    }

    public void setEncryptedPassword(String encryptedPassword) {
        this.encryptedPassword = encryptedPassword;
    }

    public Boolean getEnabled() {
        return enabled;
    }

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


    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public void addRole(Role role){
        if(!this.roles.contains(role)){
            this.roles.add(role);
        }

        if(!role.getUsers().contains(this)){
            role.getUsers().add(this);
        }
    }

    public void removeRole(Role role){
        this.roles.remove(role);
        role.getUsers().remove(this);
    }

    public Integer getFailedLoginAttempts() {
        return failedLoginAttempts;
    }

    public void setFailedLoginAttempts(Integer failedLoginAttempts) {
        this.failedLoginAttempts = failedLoginAttempts;
    }
}

Role.java

package guru.springframework.domain;

import guru.springframework.domain.AbstractDomainClass;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by jt on 12/18/15.
 */
@Entity
public class Role extends AbstractDomainClass {

    private String role;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable
    // ~ defaults to @JoinTable(name = "USER_ROLE", joinColumns = @JoinColumn(name = "role_id"),
    //     inverseJoinColumns = @joinColumn(name = "user_id"))
    private List<User> users = new ArrayList<>();

    public String getRole() {
        return role;
    }

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

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    public void addUser(User user){
        if(!this.users.contains(user)){
            this.users.add(user);
        }

        if(!user.getRoles().contains(this)){
            user.getRoles().add(this);
        }
    }

    public void removeUser(User user){
        this.users.remove(user);
        user.getRoles().remove(this);
    }

}

The User and Role JPA entities are part of the many-to-many relationship. Also, in Line 15 of the User class, notice that the password field is marked as @Transient.

That’s because we don’t want to store the password in text form.

Instead, we will store the encrypted form of the password.

JPA Repositories

Spring Data JPA provides the CRUD Repository feature. Using it, we just define the repository interfaces for our User and Role entities to extend CrudRepository.

The Spring Data JPA repositories for the User and Role entities are as follows.

UserRepository.java

package guru.springframework.repositories;

import guru.springframework.domain.User;
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User, Integer>{
    User findByUsername(String username);
}

RoleRepository.java

package guru.springframework.repositories;

import guru.springframework.domain.Role;
import org.springframework.data.repository.CrudRepository;

public interface RoleRepository extends CrudRepository<Role, Integer>{
}

By extending CrudRepository, both the repositories inherit several methods for working with entity persistence, including methods for saving, deleting, and finding entities. Spring Data JPA uses generics and reflection to generate the concrete implementations of both the interfaces.

Spring Data JPA Services

We can now create the services, that will use Spring Data JPA to perform CRUD operations on the User and Role entities.

Of course, we will follow the Interface Segregation principle to maintain loose coupling. It’s always best to “program to interface”, especially when leveraging the benefits of Spring’s dependency injection.

So, let’s start with the service interfaces.

CRUDService.java

package guru.springframework.services;

import java.util.List;

public interface CRUDService<T> {
    List<?> listAll();

    T getById(Integer id);

    T saveOrUpdate(T domainObject);

    void delete(Integer id);
}

UserService.java

package guru.springframework.services;

import guru.springframework.domain.User;

public interface UserService extends CRUDService<User> {

    User findByUsername(String username);

}

RoleService.java

package guru.springframework.services;

import guru.springframework.domain.Role;

public interface RoleService extends CRUDService<Role> {
}

Both RoleService and UserService extends CRUDService that defines the basic CRUD operations on entities. UserService, with the additional findByUsername() method is a more specialized service interface for CRUD operations on User.

We have made the service interfaces generic to mask our service implementations using the Façade design pattern. The implementations can be Spring Data JPA with repository, DAO, or Map patterns, or even plain JDBC, or some external Web service. The client code does not need not to be aware of the implementation. By using interfaces, we are able to leverage multiple concrete implementations of the services.

We’ll write the service implementation classes using the Spring Data JPA repository pattern.

UserServiceImpl.java

package guru.springframework.services;

import guru.springframework.domain.User;

import guru.springframework.repositories.UserRepository;
import guru.springframework.services.UserService;
import guru.springframework.services.security.EncryptionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@Service
@Profile("springdatajpa")
public class UserServiceImpl implements UserService {

    private UserRepository userRepository;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    private EncryptionService encryptionService;

    @Autowired
    public void setEncryptionService(EncryptionService encryptionService) {
        this.encryptionService = encryptionService;
    }


    @Override
    public List<?> listAll() {
        List<User> users = new ArrayList<>();
        userRepository.findAll().forEach(users::add); //fun with Java 8
        return users;
    }

    @Override
    public User getById(Integer id) {
        return userRepository.findOne(id);
    }

    @Override
    public User saveOrUpdate(User domainObject) {
        if(domainObject.getPassword() != null){
            domainObject.setEncryptedPassword(encryptionService.encryptString(domainObject.getPassword()));
        }
        return userRepository.save(domainObject);
    }
    @Override
      @Transactional
       public void delete(Integer id) {
        userRepository.delete(id);
    }

    @Override
    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }
}

In this class, we auto-wired in UserRepository and EncryptionService. Going ahead, we will create EncryptionService using the Jasypt library to add encryption capabilities for storing user passwords. The overridden methods of this class use the UserRepository we created to perform CRUD operations on User.

The RoleServiceImpl provides a similar implementation for RoleService.

RoleServiceImpl.java

package guru.springframework.services;

import guru.springframework.domain.Role;
import guru.springframework.repositories.RoleRepository;
import guru.springframework.services.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@Profile("springdatajpa")
public class RoleServiceImpl implements RoleService {

    private RoleRepository roleRepository;

    @Autowired
    public void setRoleRepository(RoleRepository roleRepository) {
        this.roleRepository = roleRepository;
    }

    @Override
    public List<?> listAll() {
        List<Role> roles = new ArrayList<>();
        roleRepository.findAll().forEach(roles::add);
        return roles;
    }

    @Override
    public Role getById(Integer id) {
        return roleRepository.findOne(id);
    }

    @Override
    public Role saveOrUpdate(Role domainObject) {
        return roleRepository.save(domainObject);
    }

    @Override
    public void delete(Integer id) {
        roleRepository.delete(id);
    }
}

Password Encryption Service

The Jasypt library provides an implementation for unidirectional encryption. We will use Jasypt to encrypt a password before storing it to the database. For authentication, we will provide Jasypt the received password. Under the hood, Jasypt will encrypt the received password and compare it to the stored one.

Let’s add the Jasypt dependency to our Maven POM.

<dependency>
      <groupId>org.jasypt</groupId>
      <artifactId>jasypt</artifactId>
      <version>1.9.2</version>
</dependency>
<dependency>
      <groupId>org.jasypt</groupId>
      <artifactId>jasypt-springsecurity3</artifactId>
      <version>1.9.2</version>
</dependency>

Note: The latest available Jasypt 1.9.2 targets Spring Security 3. But even for Spring Security 4 that we are using, Jasypt doesn’t have compatibility issues.

With Jasypt pulled in, we will write a bean for StrongPasswordEncryptor of Jasypt – a utility class for easily performing high-strength password encryption and checking. The configuration class, CommonBeanConfig is this.

CommonBeanConfig.java

package guru.springframework.config;

import org.jasypt.util.password.StrongPasswordEncryptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CommonBeanConfig {

    @Bean
    public StrongPasswordEncryptor strongEncryptor(){
        StrongPasswordEncryptor encryptor = new StrongPasswordEncryptor();
        return encryptor;
    }
}

Our generic EncryptionService interface will define two methods to encrypt and compare passwords.

EncryptionService.java

package guru.springframework.services.security;

public interface EncryptionService {
    String encryptString(String input);
    boolean checkPassword(String plainPassword, String encryptedPassword);
}

The implementation class is this.

EncryptionServiceImpl.java

package guru.springframework.services.security;

import org.jasypt.util.password.StrongPasswordEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class EncryptionServiceImpl implements EncryptionService {

    private StrongPasswordEncryptor strongEncryptor;

    @Autowired
    public void setStrongEncryptor(StrongPasswordEncryptor strongEncryptor) {
        this.strongEncryptor = strongEncryptor;
    }

    public String encryptString(String input){
        return strongEncryptor.encryptPassword(input);
    }

    public boolean checkPassword(String plainPassword, String encryptedPassword){
        return strongEncryptor.checkPassword(plainPassword, encryptedPassword);
    }
}

In this implementation class, we autowired the StrongPasswordEncryptor bean. In Line 18, the encryptPassword() method encrypts the password passed to it. In Line 22, the checkPassword() method returns a boolean result of the password comparison.

User Details Service Implementation

Spring Security provides a UserDetailsService interface to lookup the username, password and GrantedAuthorities for any given user. This interface provides only one method, loadUserByUsername(). This method returns an implementation of Spring Security’s UserDetails interface that provides core user information.

The UserDetails implementation of our application is this.

UserDetailsImpl.java

package guru.springframework.services.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;


public class UserDetailsImpl implements UserDetails {

    private Collection<SimpleGrantedAuthority> authorities;
    private String username;
    private String password;
    private Boolean enabled = true;

    public void setAuthorities(Collection<SimpleGrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    public void setUsername(String username) {
        this.username = username;
    }

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

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

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return enabled;
    }


}

In this class, we have defined the fields of our data model and their corresponding setter methods. The SimpleGrantedAuthority we set on Line 16 is a Spring Security implementation of an authority that we will convert from our role. Think of an authority as being a “permission” or a “right” typically expressed as strings.

We need to provide an implementation of the loadUserByUsername() method of UserDetailsService. But the challenge is that the findByUsername() method of our UserService returns a User entity, while Spring Security expects a UserDetails object from the loadUserByUsername() method.

We will create a converter for this to convert User to UserDetails implementation.

UserToUserDetails.java

package guru.springframework.converters;

import guru.springframework.domain.User;
import guru.springframework.services.security.UserDetailsImpl;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;

@Component
public class UserToUserDetails implements Converter<User, UserDetails> {
    @Override
    public UserDetails convert(User user) {
        UserDetailsImpl userDetails = new UserDetailsImpl();

        if (user != null) {
            userDetails.setUsername(user.getUsername());
            userDetails.setPassword(user.getEncryptedPassword());
            userDetails.setEnabled(user.getEnabled());
            Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
            user.getRoles().forEach(role -> {
                authorities.add(new SimpleGrantedAuthority(role.getRole()));
            });
            userDetails.setAuthorities(authorities);
        }

        return userDetails;
    }
}

This class implements the Spring Core Converter interface and overrides the convert() method that accepts a User object to convert. In Line 16, the code instantiates a UserDetailsImpl object, and from Line 19 – Line 26, the code initializes the UserDetailsImpl object with data from User.

With the converter ready, it’s now easy to implement the UserDetailsService interface. The implementation class is this.

Here is our implementation.

UserDetailsServiceImpl.java

package guru.springframework.services.security;

import guru.springframework.domain.User;
import guru.springframework.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    private UserService userService;
    private Converter<User, UserDetails> userUserDetailsConverter;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Autowired
    @Qualifier(value = "userToUserDetails")
    public void setUserUserDetailsConverter(Converter<User, UserDetails> userUserDetailsConverter) {
        this.userUserDetailsConverter = userUserDetailsConverter;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userUserDetailsConverter.convert(userService.findByUsername(username));
    }
}

In the UserDetailsServiceImpl class, we auto-wired in UserService and Converter. In Line 31, the lone overridden method loadUserByUsername() converts a User to UserDetails by calling the convert() method of Converter.

Security Configuration

The current security configuration class, SpringSecConfig extends WebSecurityConfigurerAdapter to configure two things. An authentication provider and the application routes to protect. Our route configuration will remain the same. However, we need to register the DAO authentication provider for use with Spring Security.

We will start by setting up a password encoder to encode passwords present in the UserDetails object returned by the configuredUserDetailsService. We will define a new bean for Spring Security’s PasswordEncoder that takes in the StrongPassordEncryptor bean.

Remember that we created StrongPassordEncryptor earlier in the CommonBeanConfig Spring configuration class?

@Bean
public PasswordEncoder passwordEncoder(StrongPasswordEncryptor passwordEncryptor){
    PasswordEncoder passwordEncoder = new PasswordEncoder();
    passwordEncoder.setPasswordEncryptor(passwordEncryptor);
    return passwordEncoder;
}

Next, we will set up the DAO authentication provider, like this.

@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService){

    DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
    daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
    daoAuthenticationProvider.setUserDetailsService(userDetailsService);
    return daoAuthenticationProvider;
}

In this code, we passed the previously configured PasswordEncoder and UserDetailsService to daoAuthenticationProvider(). The PasswordEncoder is going to use the Jasypt library for encoding the password and verifying that the passwords match. The UserDetailsService will fetch the User object from the database and hand over to Spring Security as a UserDetails object. In the method, we instantiated the DaoAuthenticationProvider and initialized it with the PasswordEncoder and UserDetailsService implementations.

Next, we need to auto-wire in the AuthenticationProvider as we want the Spring Context to manage it.

private AuthenticationProvider authenticationProvider;
@Autowired
@Qualifier("daoAuthenticationProvider")
public void setAuthenticationProvider(AuthenticationProvider authenticationProvider) {
    this.authenticationProvider = authenticationProvider;
}

We will also auto wire in the AuthenticationManagerBuilder. Spring Security will use this to set up the AuthenticationProvider.

@Autowired
public void configureAuthManager(AuthenticationManagerBuilder authenticationManagerBuilder){
    authenticationManagerBuilder.authenticationProvider(authenticationProvider);
}

The complete SpringSecConfig class is this.

SpringSecConfig.java

package guru.springframework.config;

import org.jasypt.springsecurity3.authentication.encoding.PasswordEncoder;
import org.jasypt.util.password.StrongPasswordEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

@Configuration
public class SpringSecConfig extends WebSecurityConfigurerAdapter {

    private AuthenticationProvider authenticationProvider;

    @Autowired
    @Qualifier("daoAuthenticationProvider")
    public void setAuthenticationProvider(AuthenticationProvider authenticationProvider) {
        this.authenticationProvider = authenticationProvider;
    }

    @Bean
    public PasswordEncoder passwordEncoder(StrongPasswordEncryptor passwordEncryptor){
        PasswordEncoder passwordEncoder = new PasswordEncoder();
        passwordEncoder.setPasswordEncryptor(passwordEncryptor);
        return passwordEncoder;
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider(PasswordEncoder passwordEncoder,
                                                               UserDetailsService userDetailsService){

        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        return daoAuthenticationProvider;
    }

    @Autowired
    public void configureAuthManager(AuthenticationManagerBuilder authenticationManagerBuilder){
        authenticationManagerBuilder.authenticationProvider(authenticationProvider);
    }
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
           httpSecurity
                .authorizeRequests().antMatchers("/","/products","/product/show/*","/console/*","/h2-console/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage("/login").permitAll()
                .and()
                .logout().permitAll();

        httpSecurity.csrf().disable();
        httpSecurity.headers().frameOptions().disable();
    }


}

Application Bootstrapping with Seed Data

For seed data of the application, we have an ApplicationListener implementation class that gets called upon the ContextRefresedEvent on startup. In this class, we will use Spring to inject the UserRepository and RoleRepository Spring Data JPA repositories for our use. We will create two User and two Role entities and save them to the database when the application starts. The code of this class is this.

SpringJpaBootstrap.java

package guru.springframework.bootstrap;

import guru.springframework.domain.Product;
import guru.springframework.domain.Role;
import guru.springframework.domain.User;
import guru.springframework.repositories.ProductRepository;
import guru.springframework.services.RoleService;
import guru.springframework.services.UserService;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.List;

@Component
public class SpringJpaBootstrap implements ApplicationListener<ContextRefreshedEvent> {

    private ProductRepository productRepository;
    private UserService userService;
    private RoleService roleService;

    private Logger log = Logger.getLogger(SpringJpaBootstrap.class);

    @Autowired
    public void setProductRepository(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Autowired
    public void setRoleService(RoleService roleService) {
        this.roleService = roleService;
    }


    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        loadProducts();
        loadUsers();
        loadRoles();
        assignUsersToUserRole();
        assignUsersToAdminRole();
    }

    private void loadProducts() {
        Product shirt = new Product();
        shirt.setDescription("Spring Framework Guru Shirt");
        shirt.setPrice(new BigDecimal("18.95"));
        shirt.setImageUrl("http://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_shirt-rf412049699c14ba5b68bb1c09182bfa2_8nax2_512.jpg");
        shirt.setProductId("235268845711068308");
        productRepository.save(shirt);

        log.info("Saved Shirt - id: " + shirt.getId());

        Product mug = new Product();
        mug.setDescription("Spring Framework Guru Mug");
        mug.setImageUrl("http://springframework.guru/wp-content/uploads/2015/04/spring_framework_guru_coffee_mug-r11e7694903c348e1a667dfd2f1474d95_x7j54_8byvr_512.jpg");
        mug.setProductId("168639393495335947");
        mug.setPrice(new BigDecimal("11.95"));
        productRepository.save(mug);

        log.info("Saved Mug - id:" + mug.getId());
    }

    private void loadUsers() {
        User user1 = new User();
        user1.setUsername("user");
        user1.setPassword("user");
        userService.saveOrUpdate(user1);

        User user2 = new User();
        user2.setUsername("admin");
        user2.setPassword("admin");
        userService.saveOrUpdate(user2);

    }

    private void loadRoles() {
        Role role = new Role();
        role.setRole("USER");
        roleService.saveOrUpdate(role);
        log.info("Saved role" + role.getRole());
        Role adminRole = new Role();
        adminRole.setRole("ADMIN");
        roleService.saveOrUpdate(adminRole);
        log.info("Saved role" + adminRole.getRole());
    }
    private void assignUsersToUserRole() {
        List<Role> roles = (List<Role>) roleService.listAll();
        List<User> users = (List<User>) userService.listAll();

        roles.forEach(role -> {
            if (role.getRole().equalsIgnoreCase("USER")) {
                users.forEach(user -> {
                    if (user.getUsername().equals("user")) {
                        user.addRole(role);
                        userService.saveOrUpdate(user);
                    }
                });
            }
        });
    }
    private void assignUsersToAdminRole() {
        List<Role> roles = (List<Role>) roleService.listAll();
        List<User> users = (List<User>) userService.listAll();

        roles.forEach(role -> {
            if (role.getRole().equalsIgnoreCase("ADMIN")) {
                users.forEach(user -> {
                    if (user.getUsername().equals("admin")) {
                        user.addRole(role);
                        userService.saveOrUpdate(user);
                    }
                });
            }
        });
    }
}

This class in addition to loading product data, invokes the following methods to load users and roles at startup:

  • loadUsers(): Stores two User entities. One with “user” and the other with “admin” as both the user name and password.
  • loadRoles(): Stores two Role entities for the “USER” and “ADMIN” roles.
  • assignUsersToUserRole(): Assigns the User with username “user” to the “USER” role.
  • assignUsersToAdminRole(): Assigns the User with username “admin” to the “ADMIN” role.

Thymeleaf Extras module

In the previous part 5 of this series, I discussed the Thymeleaf “extras” integration module to integrate Spring Security in our Thymeleaf templates. Things will largely remain unchanged in this presentation layer, except for two instances.
Currently, both USER and ROLE are being referred from the presentation layer code as ROLE_USER and ROLE_ADMIN. This was required because we relied on Spring Security’s in-memory authentication provider for managing our users and roles, and Spring Security’s internal feature maps a configured role to the role name prefixed with ROLE_. With the DAO authentication provider, our roles are mapped to authorities as it is (We did this in in the UserToUserDetails converter), and we can refer them directly from code as USER and ADMIN.

The second change is brought in by GrantedAuthority used by the Spring Security UserDetails interface. If you recall, we mapped our Role implementation to SimpleGrantedAuthority in the UserToUserDetails converter.

Therefore, in the Thymeleaf templates, we need to change the hasRole() and hasAnyRole() authorization expressions to hasAuthority() and hasAnyAuthorities().

The affected templates are header.html and products.html.

header.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head lang="en">
    <link rel="stylesheet" type="text/css" href="../static/css/guru.css" />
</head>
<body>

<div class="container">
    <div th:fragment="header">
        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <div class="navbar-header">
                    <a class="navbar-brand" href="#" th:href="@{/}">Home</a>
                    <ul class="nav navbar-nav">
                        <li><a href="#" th:href="@{/products}">Products</a></li>
                        <li><a href="#" th:href="@{/product/new}" sec:authorize="hasAuthority('ADMIN')">Create Product</a></li>
                        <li><a href="#" th:href="@{/login}">Sign In</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <div class="welcome">
            <span sec:authorize="isAuthenticated()">Welcome <span sec:authentication="name"></span></span>
        </div>
        <div class="jumbotron">
            <div class="row text-center">
                <div class="">
                    <h2>Spring Framework Guru</h2>

                    <h3>Spring Boot Web App</h3>
                </div>
            </div>
            <div class="row text-center">
                <img src="../../static/images/NewBannerBOOTS_2.png" width="400"
                     th:src="@{/images/NewBannerBOOTS_2.png}"/>
            </div>
        </div>
    </div>
</div>

</body>
</html>

products.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head lang="en">

    <title>Spring Framework Guru</title>

    <!--/*/ <th:block th:include="fragments/headerinc :: head"></th:block> /*/-->
</head>
<body>
<div class="container">
    <!--/*/ <th:block th:include="fragments/header :: header"></th:block> /*/-->



    <div th:if="${not #lists.isEmpty(products)}">
        <form th:action="@{/logout}" method="post">
            <div class="col-sm-10"><h2>Product Listing</h2></div>
            <div class="col-sm-2" style="padding-top: 30px;">
                     <span sec:authorize="isAuthenticated()">


                    <input type="submit" value="Sign Out" />
                               </span>
            </div>
        </form>
        <table class="table table-striped">
            <tr>
                <th>Id</th>
                <th>Product Id</th>
                <th>Description</th>
                <th>Price</th>
                <th sec:authorize="hasAnyAuthority('USER','ADMIN')">View</th>
                <th sec:authorize="hasAuthority('ADMIN')">Edit</th>
                <th sec:authorize="hasAuthority('ADMIN')">Delete</th>
            </tr>
            <tr th:each="product : ${products}">
                <td th:text="${product.id}"><a href="/product/${product.id}">Id</a></td>
                <td th:text="${product.productId}">Product Id</td>
                <td th:text="${product.description}">descirption</td>
                <td th:text="${product.price}">price</td>
                <td sec:authorize="hasAnyAuthority('USER','ADMIN')"><a th:href="${'/product/show/' + product.id}">View</a></td>
                <td sec:authorize="hasAuthority('ADMIN')"><a th:href="${'/product/edit/' + product.id}">Edit</a></td>
                <td sec:authorize="hasAuthority('ADMIN')"><a th:href="${'/product/delete/' + product.id}">Delete</a></td>
            </tr>
        </table>

    </div>
</div>

</body>
</html>

Running the Application

Our application is configured to run the H2 database console, which I have explained here. So, when you run the application, you’ll now be able to access the H2 database console at http://localhost:8080/console. You can use it to view the initial authentication-related data loaded by the SpringJpaBootstrap class.
USER Table for Spring Security
Role Table for Spring Security

This is how the home page appears to authenticated users with USER and ADMIN roles.

Home Page View for USER Role
Home Page View for ADMIN Role

With our Security configuration, this is how the product listing page appears to users with different roles.

Home Page View for Anonymous
Home Page View for USER Role
Home Page View for ADMIN Role

Summary

Spring Security has a large scope, and what we configured is only a small part of it. Spring Security supports XML-based and annotation-based finer level security configurations. With Spring Security, we can secure websites down to specific URLs, assign roles to URL, and even roles to different HTTP actions – a security configuration typically employed in RESTful APIs.

What makes Spring Security great is that you can easily hook in another security provider. If you noticed, we hardly made any change in the presentation and business logic layers while transitioning from the earlier basic in-memory authentication provider to the DAO provider. We could also use LDAP, Single Sign-On (SSO), OpenID, and OAuth 2.0 providers. It all depends on the requirements of your application.

Get the Source!

The full source code for this example is available here on GitHub.

About jt

    You May Also Like

    48 comments on “Spring Boot Web Application, Part 6 – Spring Security with DAO Authentication Provider

    1. February 9, 2017 at 1:11 am

      JT thank you for your post, could you post the link for source? I may missing something here, my UserService isn’t autowiring.

      Reply
    2. February 15, 2017 at 10:54 am

      You made my day

      Reply
    3. February 16, 2017 at 12:10 am

      Great tutorial!!!

      Reply
    4. March 14, 2017 at 10:36 pm

      Owesome Post.

      I new on spring boot and this web have a lot of information, well im following your tutorial, but i just can make it works ones. Now when i try to start i see this:

      Whitelabel Error Page

      This application has no explicit mapping for /error, so you are seeing this as a fallback.
      Tue Mar 14 21:48:53 GMT-05:00 2017
      There was an unexpected error (type=Internal Server Error, status=500).
      No message available

      What do you think could cause this issue.

      Have a nice day.

      Reply
    5. March 15, 2017 at 4:26 pm

      Im new on this but i can solve this on application.properties i put nex:

      spring.mvc.view.prefix=/WEB-INF/jsp/
      spring.mvc.view.suffix=.jsp

      so, it works

      Reply
    6. March 23, 2017 at 7:30 am

      how to we remove the in-memory DB and use Postgresql for example?

      Reply
      • March 23, 2017 at 12:11 pm

        It’s just a matter of wiring in a different data source. I have examples on this site for most major databases.

        Reply
    7. March 30, 2017 at 3:52 pm

      Hi, congradulatios by post! When running, the respective error:

      Description:

      Parameter 0 of method setUserService in com.beerapp.bootstrap.SpringJpaBootstrap required a bean of type ‘com.beerapp.service.UserService’ that could not be found.

      Action:

      Consider defining a bean of type ‘com.beerapp.service.UserService’ in your configuration.

      Reply
      • March 31, 2017 at 9:18 am

        Are you having the UserService interface inside com.beerapp.service.?

        Also, have you tried running the source code on github to replicate the issue?

        Reply
        • March 31, 2017 at 1:22 pm

          Are you having the UserService interface inside com.beerapp.service.?

          yes

          Also, have you tried running the source code on github to replicate the issue?

          yes, dont not problem. But following the post, there is no implementation of the mapservices package, but no git yes.
          I’ll add the same.

          Reply
          • June 21, 2017 at 10:07 am

            I have the similar problem after adding in the remaining packages that were not shown in the tutorial. I do not have any problems with the copy from git though. However, I’ll like to know how do you solve it?

            Hope to hear from anyone who have this issue too. Thanks

            Reply
            • June 29, 2017 at 5:46 pm

              I had the same issue.

              I didn’t added the remaining packages located on github, just deleting the annotation @Profile(…) did the trick.

              (I didn’t understand what it did anyway)

        • July 3, 2017 at 1:59 pm

          To those of you having this problem, the fix is to use the profile ‘springdatajpa’.
          In order to do this, you’ll have to make the edit in application.properties file. Add this line:
          spring.profiles.active=springdatajpa

          if you notice in the code on githib, he has other profiles available to use such as ‘map’ and ‘jpadao’

          Reply
      • May 23, 2021 at 11:37 pm

        Yes, same issue with mine.

        Is there any ideas or suggestions?

        Thanks

        Reply
      • May 23, 2021 at 11:39 pm

        I was trying to update the code with latest Spring Boot version (2.5.0).

        But, still I have error with following error:
        “`
        Field userService in com.hendisantika.webapp.bootstrap.SpringJpaBootstrap required a bean of type ‘com.hendisantika.webapp.service.UserService’ that could not be found.

        The injection point has the following annotations:
        – @org.springframework.beans.factory.annotation.Autowired(required=true)

        Action:

        Consider defining a bean of type ‘com.hendisantika.webapp.service.UserService’ in your configuration.
        “`

        Any ideas or advice?

        Thanks

        Reply
    8. May 1, 2017 at 10:42 pm

      Hi JT, thank you very much for the great posts with clear explanations to help newcomers like to understand the idea of the implementation details.

      For the JPA entities User.java and Role.java, are we expecting only 1 table “USER_ROLE” generated as the results of the ManyToMany annotations defined in both the entities, or 2 separate tables of “USER_ROLES” and “ROLE_USERS”?

      After testing it out, my testdb contains 2 separate tables of “USER_ROLES” and “ROLE_USERS” with only 1 of the tables having records created.

      Thanks again.

      Reply
      • July 11, 2017 at 2:06 am

        Hi Jt.. i want to know the answer to thanks ^^

        Reply
      • July 15, 2017 at 3:56 pm

        I was also scratching my head over this as I am new to JPA. ROLE_USER table seems to be redundant.
        You can simply remove @JoinTable annotation from Role class and modify @ManyToMany(fetch = FetchType.EAGER) annotation to make it looks like this @ManyToMany(fetch = FetchType.EAGER, mappedBy = “roles”).
        Thanks to removing @JoinTable, ROLE_USER table will not be created and adding mapped=”roles” tells that the relation is handled by ‘roles’ list in User class. User class does not need to be modified at all.
        It seems to work for me 🙂

        Reply
    9. July 3, 2017 at 2:19 pm

      Thank you so much for this amazing tutorial. I’ve used Spring for 5 years now. I can’t believe how much configuration headache Spring Boot removes. And your tutorial was very clean and precise. It really took all the headache away from having to learn another framework. One of the best I’ve seen.

      Reply
      • July 3, 2017 at 2:26 pm

        Thanks Bob!!

        Reply
    10. July 11, 2017 at 4:41 am

      Hi JT..

      therese 1 thing i still dont quite understand..

      in user repository there 1 custom method findByUsername

      what class that implement it? how can it work when no class implement it..

      thanks before

      Reply
      • July 11, 2017 at 8:52 am

        Spring Data JPA provides the implementation for you at run time.

        Reply
        • July 11, 2017 at 9:08 am

          I see so spring data jpa already have implement class for findbyusername..

          If i want to make new method like findByUsernameAndPassword.. how should i do? Should i implementing it myself?

          Thanks again jt..

          Reply
    11. July 28, 2017 at 1:40 am

      Thanks for the Article – Can you please give a tutorial for OAuth 2.0 as well.

      Reply
    12. August 3, 2017 at 12:06 am

      I have a question that why in class SpringSecConfig , you don’t need @EnableWebSecurity annotation?, and when we have to add that annotation to our security configuration class.

      Reply
    13. October 19, 2017 at 7:21 am

      Hi JT, great tutorial!
      Is it considered good practice to call an objects domain methods from within a thymeleaf template?

      For example, suppose I have a user controller that binds all users to the model returned to the view. In that thymeleaf view, I would like to iterate over all users and for each user list their role(s). Is it ok to call the user.getRoles() method in thymeleaf, or would it be considered coupling the view too tightly to the controller? Is there a better or best practice way?

      Cheers,
      Brad

      Reply
      • October 19, 2017 at 10:09 am

        Yes – that is fine. On larger applications, its common to expose DTOs to the view layer. But in smaller applications, its fine to use the domain object.

        Reply
        • October 19, 2017 at 1:27 pm

          Thanks for the reply!

          I am also a bit confused as to where the business logic belongs in the structure of MVC. For example, if I have a method that should be called every time a product is updated that manipulates that product in some way, where should that method be located? Also in the objects entity domain class?

          Some sources I have read state that the business logic should be in the domain classes, others say in the Service layer. I am having a difficult time distinguishing the difference…

          Thanks,
          Brad

          Reply
          • October 19, 2017 at 3:16 pm

            Service layer. Domain objects are data structures.

            Reply
    14. November 11, 2017 at 10:21 pm

      Why Impl? there is no point of creating an interface for a service which has only a single known implementation.

      Reply
      • November 13, 2017 at 11:46 am

        Its always a best practice to code against interface instead of concrete implementation. Your client code remains untouched if you decide for another implementation tomorrow.

        Reply
    15. November 18, 2017 at 9:00 am

      Hello, If i want to use mysql for data, how i can do it?? please help me.

      Reply
      • November 20, 2017 at 9:59 am

        This is simple to do. I have several examples – just search my blog for mysql.

        Reply
    16. November 19, 2017 at 3:13 pm

      Dear Jt, why do not you explain mapservice and jpaservice, i see it in your project, but i dont understand it?? please help me

      Reply
      • November 20, 2017 at 10:00 am

        These are examples used in my courses – in which, I explain their use in depth.

        Reply
    17. January 7, 2018 at 9:24 pm

      JT – superb example project, especially the JPA annotations, DaoAuthentication setup, & Thymeleaf presentation. Many thanks!

      Reply
    18. February 4, 2018 at 6:05 pm

      Hello! Can you please help me with a problem? I just simple cannot understand why Spring autowires AuthenticationManagerBuilder and only after that autowires the AuthenticationProvider.
      With other word, first it enters in :
      @Autowired
      public void configureAuthManager(AuthenticationManagerBuilder authenticationManagerBuilder){
      authenticationManagerBuilder.authenticationProvider(authenticationProvider);
      }

      and only after in
      @Autowired
      @Qualifier(“daoAuthenticationProvider”)
      public void setAuthenticationProvider(AuthenticationProvider authenticationProvider) {
      this.authenticationProvider = authenticationProvider;
      }

      I also followed you course “Spring advanced” from udemy and I have the same problem.
      Many thanks in advance!

      Reply
    19. February 7, 2018 at 12:44 pm

      Hi Jt,
      I’m still wondering where the link between the AuthenticationManagerBuilder, Datasource(H2 Database) and DaoAuthenticationProvider is done ? Because I didn’t see how you configure the datasource in your code. Let consider the case that I’m using MySQL, where will be the configuration of the datasoruce ? Thanks in advance.

      Reply
    20. February 17, 2018 at 5:37 am

      Great tuto and i’ve enjoyed reading. from the part 1 to 6 it was precise, well documented and so easy even for newbie in spring boot like me.
      very good Job JT.

      how ever, for spring security part, the dependency for jasypt has evolved and spring official documentation recommends to use BCryptPasswordEncoder instead of PasswordEncoder you used. I’ve done so, but facing invalid username and password due to “Encoded password does not look like BCrypt”.

      May be have missed some conf in the secConfiguration class?

      Reply
    21. August 24, 2018 at 3:40 am

      jasypt doesnt compatible with spring-security-core: 5.x.x

      Reply
    22. August 31, 2018 at 12:42 pm

      I really don’t understand why do you comment out @JoinTable(name = “USER_ROLE”, joinColumns = @JoinColumn(name = “xxx_id”), inverseJoinColumns = @JoinColumn(name = “yyy_id”)) in User and Role domain objects? Without that we got two onetomany relationships instead of one manytomany

      Reply
    23. May 3, 2020 at 4:28 am

      Could you please show a demo app that uses DAOAuthenticationProvider along with SAMLAuthenticationProvider.
      Use Case: Based on the username entered I would like to switch between these two auth providers.

      Reply
    24. May 31, 2021 at 4:11 pm

      Can you please merge the branches

      Reply

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.