Spring Boot User Login and Register Example

 Spring Boot User Login and Register Example:


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.


Libraries / Dependencies:

1)    Spring Web: Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.

2)    Thymeleaf: modern server-side Java template engine. Allow HTML files to correctly displayed in browsers and as static prototypes.

3)    Spring Data JPA: Persist data in SQL stores with Java Persistence API using Spring Data and Hibernate.

4)    MySQL Driver: A JDBC driver that allows Java programs to connect to a MySQL database using standard, database independent Java code.

5)    Spring Security: Highly customizable authentication and access-control framework for Spring applications.

6)    Validation: Bean Validation with Hibernate validator.


Note: Please add the database configuration in you application.properties

Example:

server.port=8080

server.error.include-message=always

logging.level.org.hibernate.SQL=DEBUG

logging.level.org.hibernate.type=TRACE

logging.level.org.springframework.web=DEBUG

logging.level.org.hibernate=ERROR

spring.datasource.url=jdbc:mysql://localhost:3306/databasename

spring.datasource.username=user

spring.datasource.password=password

spring.jpa.show-sql=true

spring.jpa.hibernate.ddl-auto=update

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

spring.jpa.properties.hibernate.format_sql=true


Project 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:

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

<!--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

<!--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.

<!--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

<!--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

<!--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 AdminControlleradmin/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 UserControlleruser/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.

Comments