In this tutorial, you will learn how to create Login and Register functionality in spring boot. You can use this tutorial with different databases like PostgreSQL or MySQL and build tools like gradle or maven. This tutorial is implemented on Maven, PostgreSQL, SecurityFilterChain and Standard Folder Structure. You will find how to use User Role and how to restrict users from accessing pages based on role associated to account. This is a complete tutorial from login / signup to home and dashboard with email login. So let’s get start…
Libraries / Dependencies
- Spring Web: Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.
- Thymeleaf: modern server-side Java template engine. Allow HTML files to correctly displayed in browsers and as static prototypes.
- Spring Data JPA: Persist data in SQL stores with Java Persistence API using Spring Data and Hibernate.
- PostgreSQL Driver: A JDBC and R2DBC driver that allows Java programs to connect to a PostgreSQL database using standard, database independent Java code.
- Spring Security: Highly customizable authentication and access-control framework for Spring applications.
- Validation: Bean Validation with Hibernate validator.
Download the starter project with all dependencies from here.
Note: Please add the database configuration in you application.properties
. Find a example here.
Folder Structure
Bellow is the folder structure used for developing the login register project.
src
└── main
├── java
│ └── com
│ └── example
│ └── demo
│ ├── LoginRegisterApplication.java
│ ├── config
│ │ ├── CustomLoginSucessHandler.java
│ │ ├── WebMvcConfig.java
│ │ └── WebSecurityConfig.java
│ ├── controller
│ │ ├── AdminController.java
│ │ ├── AuthController.java
│ │ └── UserController.java
│ ├── model
│ │ ├── Role.java
│ │ └── User.java
│ ├── repository
│ │ └── UserRepository.java
│ └── service
│ ├── UserServiceImpl.java
│ └── UserService.java
└── resources
├── application.properties
├── static
└── templates
├── about-us.html
├── access-denied.html
├── homepage.html
├── admin
│ └── dashboard.html
├── auth
│ ├── login.html
│ └── register.html
└── user
└── dashboard.html
User and Role Model
Lets create a User.java
which implements UserDetails
.
package com.example.demo.model;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.validator.constraints.Length;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
@Entity
@Table(name = "users")
public class User implements UserDetails {
@SequenceGenerator(
name = "users_sequence",
sequenceName = "users_sequence",
allocationSize = 1
)
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "users_sequence"
)
private int id;
@NotNull(message = "First Name cannot be empty")
@Column(name = "first_name")
private String firstName;
@NotNull(message = "Last Name cannot be empty")
@Column(name = "last_name")
private String lastName;
@NotNull(message = "Email cannot be empty")
@Email(message = "Please enter a valid email address")
@Column(name = "email", unique = true)
private String email;
@NotNull(message = "Password cannot be empty")
@Length(min = 7, message = "Password should be atleast 7 characters long")
@Column(name = "password")
private String password;
@Column(name = "mobile", unique = true)
@Length(min = 10, message = "Password should be atleast 10 number long")
private String mobile;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Enumerated(EnumType.STRING)
private Role role;
@Column(name = "locked")
private Boolean locked = false;
@Column(name = "enabled")
private Boolean enabled = true;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.name());
return Collections.singletonList(authority);
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password){
this.password = password;
}
@Override
public String getUsername() {
return email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
public Role getRole() { return role; }
public void setRole(com.example.demo.model.Role role) {
this.role = role;
}
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName;}
public String getMobile() { return mobile; }
public void setMobile(String mobile) { this.mobile = mobile; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
}
Now create Role.java
Interface
package com.example.demo.model;
public enum Role {
USER("User"),
ADMIN("Admin");
private final String value;
private Role(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
User Repository Interface
Create UserRepository.java
interface which extends JpaRepository<User, Long>
. In this, we create findByEmail()
and findByMobile()
.
package com.example.demo.repository;
import com.example.demo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByMobile(String mobile);
}
User Services Interface with Implementation
Create UserService.java
interface to save user and check user existance.
package com.example.demo.service;
import com.example.demo.model.User;
import java.util.List;
public interface UserService {
public void saveUser(User user);
public List<Object> isUserPresent(User user);
}
Now create UserServiceImpl.java
which implements UserService.java
and UserDetailsService interface. In this we also override loadUserByUsername()
method
package com.example.demo.service;
import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@Service
public class UserServiceImpl implements UserService, UserDetailsService {
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
UserRepository userRepository;
@Override
public void saveUser(User user) {
String encodedPassword = bCryptPasswordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
// user.setRole(Role.USER);
userRepository.save(user);
}
@Override
public List<Object> isUserPresent(User user) {
boolean userExists = false;
String message = null;
Optional<User> existingUserEmail = userRepository.findByEmail(user.getEmail());
if(existingUserEmail.isPresent()){
userExists = true;
message = "Email Already Present!";
}
Optional<User> existingUserMobile = userRepository.findByMobile(user.getMobile());
if(existingUserMobile.isPresent()){
userExists = true;
message = "Mobile Number Already Present!";
}
if (existingUserEmail.isPresent() && existingUserMobile.isPresent()) {
message = "Email and Mobile Number Both Already Present!";
}
System.out.println("existingUserEmail.isPresent() - "+existingUserEmail.isPresent()+"existingUserMobile.isPresent() - "+existingUserMobile.isPresent());
return Arrays.asList(userExists, message);
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepository.findByEmail(email).orElseThrow(
()-> new UsernameNotFoundException(
String.format("USER_NOT_FOUND", email)
));
}
}
Configuration for Web Security, MVC and Custom Login Success Handler
Create WebSecurityConfig.java
which EnableWebSecurity Configuration. In this, we implemented SecurityFilterChain
which eleminates WebSecurityConfigurerAdapter Deprecated warning. All routes permission can be given here with authorize restriction.
package com.example.demo.config;
import com.example.demo.service.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Autowired
private CustomLoginSucessHandler sucessHandler;
@Bean
public UserDetailsService userDetailsService() {
return new UserServiceImpl();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
// URL matching for accessibility
.antMatchers("/", "/login", "/register").permitAll()
.antMatchers("/admin/**").hasAnyAuthority("ADMIN")
.antMatchers("/account/**").hasAnyAuthority("USER")
.anyRequest().authenticated()
.and()
// form login
.csrf().disable().formLogin()
.loginPage("/login")
.failureUrl("/login?error=true")
.successHandler(sucessHandler)
.usernameParameter("email")
.passwordParameter("password")
.and()
// logout
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.and()
.exceptionHandling()
.accessDeniedPage("/access-denied");
http.authenticationProvider(authenticationProvider());
http.headers().frameOptions().sameOrigin();
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/images/**", "/js/**", "/webjars/**");
}
}
Create CustomLoginSucessHandler.java
for login success url based on Role.
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Configuration
public class CustomLoginSucessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException {
String targetUrl = determineTargetUrl(authentication);
if(response.isCommitted()) return;
RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineTargetUrl(Authentication authentication){
String url = "/login?error=true";
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
List<String> roles = new ArrayList<String>();
for(GrantedAuthority a : authorities){
roles.add(a.getAuthority());
}
if(roles.contains("ADMIN")){
url = "/admin/dashboard";
}else if(roles.contains("USER")) {
url = "/dashboard";
}
return url;
}
}
Create WebMvcConfig.java
for adding views.
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/access-denied").setViewName("access-denied");
registry.addViewController("/").setViewName("homepage");
registry.addViewController("/about-us").setViewName("about-us");
}
}
In above WebMvcConfig, we create 3 view, so lets create templates for it – homepage.html,
about-us.html
and access-denied.html
.
<!--homepage.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Homepage</title>
</head>
<body>
<h2>Welcome to Homepage</h2>
<p sec:authorize="hasRole('ROLE_ANONYMOUS')">Text visible to anonymous.</p>
<p sec:authorize="hasRole('USER')">Text visible to user.</p>
<p sec:authorize="hasRole('ADMIN')">Text visible to admin.</p>
<p sec:authorize="isAuthenticated()">Text visible only to authenticated users.</p>
<div sec:authorize="hasRole('ROLE_ANONYMOUS')">
<p><a th:href="@{|/login|}" th:text="'Log in'"></a></p>
<p><a th:href="@{|/register|}" th:text="'Register'"></a></p>
</div>
<div sec:authorize="isAuthenticated()">
<p>Logged as: <span sec:authentication="name"></span></p>
<p>Has role: <span sec:authentication="authorities"></span></p>
<p sec:authorize="hasAuthority('USER')"><a th:href="@{|/dashboard|}" th:text="'User Dashboard'"></a></p>
<p sec:authorize="hasAuthority('ADMIN')"><a th:href="@{|/admin/dashboard|}" th:text="'Admin Dashboard'"></a></p>
<a th:href="@{/logout}">Log out</a>
</div>
</body>
</html>
<!--about-us.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>About Us</title>
</head>
<body>
<h2>About Us</h2>
</body>
</html>
<!--access-denied.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Access Denied</title>
</head>
<body>
<h2>Access Denied</h2>
</body>
</html>
Admin, User and Auth Controller with HTML
Now lets create AuthController.java
where will add login
and register
request mapping. In this we have create 3 request mapping where 2 are get method and 1 is post method.
package com.example.demo.controller;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.validation.Valid;
import java.util.List;
@Controller
public class AuthController {
@Autowired
UserService userService;
@RequestMapping(value = {"/login"}, method = RequestMethod.GET)
public String login(){
return "auth/login";
}
@RequestMapping(value = {"/register"}, method = RequestMethod.GET)
public String register(Model model){
model.addAttribute("user", new User());
return "auth/register";
}
@RequestMapping(value = {"/register"}, method = RequestMethod.POST)
public String registerUser(Model model, @Valid User user, BindingResult bindingResult){
if(bindingResult.hasErrors()){
model.addAttribute("successMessage", "User registered successfully!");
model.addAttribute("bindingResult", bindingResult);
return "auth/register";
}
List<Object> userPresentObj = userService.isUserPresent(user);
if((Boolean) userPresentObj.get(0)){
model.addAttribute("successMessage", userPresentObj.get(1));
return "auth/register";
}
userService.saveUser(user);
model.addAttribute("successMessage", "User registered successfully!");
return "auth/login";
}
}
In AuthController.java
we have used 2 templates login.html
and register.html
<!--login.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<form th:action="@{/login}" method="POST" class="form-signin">
<h3 th:text="Login"></h3>
<input type="text" name="email" th:placeholder="Email" /> <br /><br />
<input type="password" th:placeholder="Password" name="password" /> <br /><br />
<button type="Submit" th:text="Login"></button> | <a th:href="@{|/register|}" th:text="'Register'"></a><br />
<div th:if="${param.error}"><p>Email or Password is invalid.</p></div> <br />
</form>
</body>
</html>
<!--register.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<form th:action="@{/register}" th:object="${user}" method="post">
<h3>Registration</h3>
<input type="text" th:field="*{firstName}" placeholder="First Name" /> <br/><br/>
<input type="text" th:field="*{lastName}" placeholder="Last Name" /> <br/><br/>
<input type="text" th:field="*{mobile}" placeholder="Mobile Number" /> <br/><br/>
<input type="text" th:field="*{email}" placeholder="Email" /> <br/><br/>
<input type="password" th:field="*{password}" placeholder="Password" /> <br/><br/>
<select th:field="*{role}" required>
<option value="">Select</option>
<option value="ADMIN">ADMIN</option>
<option value="USER">USER</option>
</select> <br/><br/>
<button type="submit">Register</button> | <a th:href="@{|/login|}" th:text="'Log in'"></a>
<span th:utext="${successMessage}"></span>
<div th:if="${bindingResult!=null && bindingResult.getAllErrors()!=null}">
<ul th:each="data : ${bindingResult.getAllErrors()}">
<li th:text="${data.getObjectName() + ' :: ' + data.getDefaultMessage()}"></li>
</ul>
</div>
</form>
</body>
</html>
Lets create now AdminController.java
for creating page only view for admin users.
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class AdminController {
@RequestMapping(value = {"/admin/dashboard"}, method = RequestMethod.GET)
public String adminHome(){
return "admin/dashboard";
}
}
In AdminController
, admin/dashboard.html
template is returned
<!--admin/dashboard.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>User Dashboard</title>
</head>
<body>
<h2>Welcome to Admin Dashboard</h2>
<p>Logged as: <span sec:authentication="name"></span></p>
<p>Has role: <span sec:authentication="authorities"></span></p>
<p><a th:href="@{/}">Home</a></p>
<p><a th:href="@{/logout}">Log out</a></p>
</body>
</html>
Now lets create last controller, UserController.java
which only give User role views.
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class UserController {
@RequestMapping(value = {"/dashboard"}, method = RequestMethod.GET)
public String homePage(){
return "user/dashboard";
}
}
In UserController
, user/dashboard.html
template is returned
<!--user/dashboard.html-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>User Dashboard</title>
</head>
<body>
<h2>Welcome to User Dashboard</h2>
<p>Logged as: <span sec:authentication="name"></span></p>
<p>Has role: <span sec:authentication="authorities"></span></p>
<div>
<p><a th:href="@{/}">Home</a></p>
<p><a th:href="@{/logout}">Log out</a></p>
</div>
</body>
</html>
Done! Now you can run the project, you can successfully add users, login and view pages based on role permissions.
Download User Login Register in Spring Boot
Find this tutorial on GitHub.