Spring Boot Web Application, Part 6 – Spring Security with DAO Authentication Provider
48 CommentsLast 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’s “program 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 twoUser
entities. One with “user” and the other with “admin” as both the user name and password.loadRoles()
: Stores twoRole
entities for the “USER” and “ADMIN” roles.assignUsersToUserRole()
: Assigns theUser
with username “user” to the “USER” role.assignUsersToAdminRole()
: Assigns theUser
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.
This is how the home page appears to authenticated users with USER
and ADMIN
roles.
With our Security configuration, this is how the product listing page appears to users with different roles.
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.
Cleber Oliveira
JT thank you for your post, could you post the link for source? I may missing something here, my UserService isn’t autowiring.
Aimé D
You made my day
Geoff Washam
Great tutorial!!!
Marco
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.
Marco
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
Agustin Silva
how to we remove the in-memory DB and use Postgresql for example?
jt
It’s just a matter of wiring in a different data source. I have examples on this site for most major databases.
Jorge
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.
Simanta Sarma
Are you having the UserService interface inside com.beerapp.service.?
Also, have you tried running the source code on github to replicate the issue?
Jorge
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.
hweichou
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
Kalreg
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)
bigbob68
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’
Hendi Santika
Yes, same issue with mine.
Is there any ideas or suggestions?
Thanks
Hendi Santika
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
HoonBoon
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.
cahyo Adi Permono
Hi Jt.. i want to know the answer to thanks ^^
Tommy
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 🙂
bigbob68
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.
jt
Thanks Bob!!
cahyo Adi Permono
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
jt
Spring Data JPA provides the implementation for you at run time.
cahyo Adi Permono
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..
jt
Just follow the naming convention for query methods and Spring Data will create it for you. See: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.details
cahyo Adi Permono
Ahh… I understand now..
Thanks a lot jt 🙂
Mac Mold
Thanks for the Article – Can you please give a tutorial for OAuth 2.0 as well.
Cuong
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.
Lefteris Kororos
I had the same question. I found the answer here http://info.michael-simons.eu/2017/02/14/a-quick-note-on-spring-boot-security/ “You do not need @EnableWebSecurity because spring-boot @SpringBootApplication handles this for you.”
Brad
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
jt
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.
Brad
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
jt
Service layer. Domain objects are data structures.
maxraulMax
Why Impl? there is no point of creating an interface for a service which has only a single known implementation.
Ximanta Sarma
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.
Truong Hung
Hello, If i want to use mysql for data, how i can do it?? please help me.
jt
This is simple to do. I have several examples – just search my blog for mysql.
Trương Đức Hùng
Dear Jt, why do not you explain mapservice and jpaservice, i see it in your project, but i dont understand it?? please help me
jt
These are examples used in my courses – in which, I explain their use in depth.
Riley
JT – superb example project, especially the JPA annotations, DaoAuthentication setup, & Thymeleaf presentation. Many thanks!
Adrian Barna
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!
Gael
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.
Gregorio castillo
You have to do it in the application.properties or application.yml file. See this link https://spring.io/guides/gs/accessing-data-mysql/
Mohamed
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?
Tatan Sugiana
jasypt doesnt compatible with spring-security-core: 5.x.x
stand alone
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
chirag soni
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.
Ok
Can you please merge the branches