Spring Boot Tutorial: Create Java Login Web App using Spring Security and Eclipse

by Didin J. on Jan 30, 2020 Spring Boot Tutorial: Create Java Login Web App using Spring Security and Eclipse

A comprehensive step by step Java tutorial on creating Java login web app using Spring Boot, Web MVC, Security, Data, JDBC, Eclipse, and Spring Tools

In this tutorial, we will show you how to create a Java login web app using Spring Boot, Web MVC, Security, Data, JDBC, Eclipse, and Spring Tools. This time, we will use the H2 in-memory database that you can change to any Relational database by changing the configuration.

This tutorial divided into several steps:

The following tools, frameworks, and libraries are required for this tutorial:

  1. JDK 8
  2. Spring Initializr
  3. Spring Boot
  4. Spring Data Web
  5. Spring Data JDBC
  6. Spring Data JPA
  7. H2 Database
  8. Thymeleaf
  9. Thymeleaf layout dialect
  10. Bootstrap
  11. Eclipse IDE + Spring Tools

Before going to the main steps, make sure that you have to download and install the Java Development Kit (JDK) 8.

You can watch the video tutorial from our YouTube channel. Please like, share, comment, and subscribe to our Channel.


Step #1: Prepare Eclipse and Spring Tools

Download Eclipse installer from their official site.

https://s3-ap-southeast-1.amazonaws.com/djamblog/article-300120061932.pngCreate Java Login Web App using Spring Security and Eclipse - Eclipse IDE

Run Eclipse installer then choose the "Eclipse IDE for Java Developers".

Create Java Login Web App using Spring Security and Eclipse - Eclipse Installer

Just follow the installation wizard as the default until finished the Eclipse IDE 2019-12 installation. Next, start Eclipse IDE then click Help menu -> Eclipse Marketplace.

Create Java Login Web App using Spring Security and Eclipse - Spring Tools

Find for "Spring Tools" then install the "Spring Tools 4 (aka Spring Tool Suite 4) 4.1.1.RELEASE". Now, the Eclipse IDE with Spring Tools is ready to use. 


Step #2: Generate Spring Boot Java Web App

To generate the Spring Boot Java web app, go to Spring Initializr https://start.spring.io/ then choose the project "Build Project", Language "Java", Spring Boot version "2.2.4" (or stable release without M* or SNAPSHOT), Project Metadata Group "com.djamware", Artifact "mynotes".

Create Java Login Web App using Spring Security and Eclipse - Spring Initializr

Add the required dependencies by type in the search box then choose it. We are searching for Spring Web, Spring Security, H2 Database, Spring Data JPA, Spring Data JDBC, and Thymeleaf.

Create Java Login Web App using Spring Security and Eclipse - Dependencies

Click the Generate button to download the zipped Gradle project. Next, extract the zipped Gradle project to your Eclipse workspace. In the Eclipse, click Import then drop-down Gradle -> Existing Gradle Project.

Create Java Login Web App using Spring Security and Eclipse - Import Gradle Project

Click next to the "Import Gradle Project" step then browse for the extracted Gradle project in the Eclipse workspace then click the Finish button.

Create Java Login Web App using Spring Security and Eclipse - Import Gradle Project Browse

Wait for Gradle dependencies downloading and installing. Next, expand the "mynotes" project name in the Project Explorer then open "build.gradle" file. Add this line inside the dependencies body.

dependencies {
    ...
      compile 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:2.4.1'
    ...
}

Right-click the project name in the Project Explorer then choose Gradle -> Refresh Gradle Project to download and install the new dependencies.


Step #3: Create Java Notes, User and Role Model Class

To create a Java class, simply right-click the Project name in the Project Explorer pane then choose New -> Class.

Create Java Login Web App using Spring Security and Eclipse - New Class

Fill the Package name "com.djamware.mynotes.models", Name "Notes", modifiers "public", and leave other fields as default.

Create Java Login Web App using Spring Security and Eclipse - Java Class

Click the Finish button after fill all required fields. Do the same way for User and Role class using Name "User" and "Role". Next, go to the opened Notes.java then modify the class to be like this.

@Entity
public class Notes {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String title;

    private String content;

    private Date updated;

}

Press Command + Shift + O or choose the Source menu -> Organized Imports to add the imports. If there a choice between "javax.persistence.Entity" and "org.hibernate.annotations.Entity", just choose "javax.persistence.Entity" then click the Next button. If there a choice between "java.sql.Date" and "java.util.Date", just choose "java.util.Date" then click the Next button. Choose "javax.persistence.Table" if there show a choice of Table then click the Finish button. So, the imports will be like this.

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

Next, click Source Menu -> Generate Constructor from Superclass then click the Generate button. Click Source menu -> Generate Constructor using Fields then uncheck the ID field and checks other fields then click the Generate button.

    public Notes() {
        super();
        // TODO Auto-generated constructor stub
    }

    public Notes(String title, String content, Date updated) {
        super();
        this.title = title;
        this.content = content;
        this.updated = updated;
    }

Next, click Source menu -> Generate Getters and Setters then choose all fields then click the Generate button. 

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }

Next, go to the opened User.java tab then do the same way as we dod to the Notes.java. The most different thing is this class using Many-to-many relationship with Role class using dedicated relation table. So, it will look like this.

package com.djamware.mynotes.models;

import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name = "user")
public class User {

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

    private String email;

    private String password;

    private String fullname;

    private boolean enabled;

    @ManyToMany
    @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    private Set<Role> roles;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

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

    public String getFullname() {
        return fullname;
    }

    public void setFullname(String fullname) {
        this.fullname = fullname;
    }

    public boolean isEnabled() {
        return enabled;
    }

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

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

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

}

Next, go to the opened Role.java tab then do the same way as we dod to the User.java. The Many-to-many relationship for this class is simpler than the User class. So, the whole Role.java codes will be like this.

package com.djamware.mynotes.models;

import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name = "role")
public class Role {

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

    private String role;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRole() {
        return role;
    }

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

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

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

}

Step #4: Create Java Notes, User and Role Repository Interface

To make connectivity between Java model and the required methods, create Java Repository Interfaces for each Java model class that previously created. First, create the Java interface file by right-clicking the Project name in the Project Explorer pane then click New -> Interface.

Create Java Login Web App using Spring Security and Eclipse - New Interface

Fill the Package with "com.djamware.mynotes.repositories", Name "NotesRepository", and leave other fields as default.

Create Java Login Web App using Spring Security and Eclipse - Java Interface

Click the Finish button. Create the repository interface for User and Role by doing the same way. Give the name for User "UserRepository" and Role "RoleRepository". Next, go to the opened NotesRepository.java then modify that interface to be like this.

public interface NotesRepository extends JpaRepository<Notes, Long> {
    
    Notes findByTitle(final String title);
}

As usual, press Command + Shift + O to organize the imports. So, the imports should be like this.

import org.springframework.data.jpa.repository.JpaRepository;
import com.djamware.mynotes.models.Notes;

Do the same way to UserRepository.java, so it will look like this.

package com.djamware.mynotes.repositories;

import org.springframework.data.jpa.repository.JpaRepository;

import com.djamware.mynotes.models.User;

public interface UserRepository extends JpaRepository<User, Long> {
    
    User findByEmail(final String email);

}

And optionally to Role.java, so it will look like this.

package com.djamware.mynotes.repositories;

import org.springframework.data.jpa.repository.JpaRepository;

import com.djamware.mynotes.models.Role;

public interface RoleRepository extends JpaRepository<Role, Long> {
    
    Role findByRole(final String role);

}


Step #5: Create a Custom Java User Details Service

We need to implement our custom User models in Spring Security. So, it will expose the additional full name. For that, create a custom User details service by right-clicking the Project name in the Project Explorer. Click New -> Class then fill the required package "com.djamware.mynotes.services", Name "CustomUserDetailsService", and leave other fields as default.

Create Java Login Web App using Spring Security and Eclipse - Java Class Service

Click the Finish button and the created Java class will open. Modify the class name to add implements of Spring Security UserDetailsService.

@Service
public class CustomUserDetailsService implements UserDetailsService {

}

Declare the user and role repositories and `BCryptPasswordEncoder` for the password encryption after the class name.

@Autowired
private UserRepository userRepository;

@Autowired
private RoleRepository roleRepository;

@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;

Create a method for getting the user by email.

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

Create a method for save a new user, encrypt the password and set a role for the user. For now, we will use the role `ADMIN` for all newly registered users.

    public void saveUser(User user) {
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        user.setEnabled(true);
        Role userRole = roleRepository.findByRole("ADMIN");
        user.setRoles(new HashSet<>(Arrays.asList(userRole)));
        userRepository.save(user);
    }

Optionally, we can change the saveUser method to add a String parameter for a Role name. So, the userRole find by role name parameter that sends from the registration page.

    public void saveUser(User user, String role) {
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        user.setEnabled(true);
        Role userRole = roleRepository.findByRole(role);
        user.setRoles(new HashSet<>(Arrays.asList(userRole)));
        userRepository.save(user);
    }

Create a method for handling the login mechanism that checks or compares usernames with the user from the Database table.

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

        User user = userRepository.findByEmail(email);  
        if(user != null) {
            List<GrantedAuthority> authorities = getUserAuthority(user.getRoles());
            return buildUserForAuthentication(user, authorities);
        } else {
            throw new UsernameNotFoundException("username not found");
        }
    }

That method has a method for converting the user roles as GrantedAuthority collection. Create a new method like this.

    private List<GrantedAuthority> getUserAuthority(Set<Role> userRoles) {
        Set<GrantedAuthority> roles = new HashSet<>();
        userRoles.forEach((role) -> {
            roles.add(new SimpleGrantedAuthority(role.getRole()));
        });

        List<GrantedAuthority> grantedAuthorities = new ArrayList<>(roles);
        return grantedAuthorities;
    }

Next, add the method to Spring Security user details based on user model email and password.

    private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
        return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
    }

As usual, press Command + Shift + O to organize imports. So, the whole imports like these.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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 com.djamware.mynotes.models.Role;
import com.djamware.mynotes.models.User;
import com.djamware.mynotes.repositories.RoleRepository;
import com.djamware.mynotes.repositories.UserRepository;


Step #6: Create Controller for All Spring MVC Views

To accessing or manage between views and models we have to create a controller. We will put all of Login, and Register in one controller. For that, right-click the project name on Project explorer then click New -> Class. On the new Java Class form, fill Class Name with "AuthController" and package name "com.djamware.mynotes.controller" then click the Finish button. 

Create Java Login Web App using Spring Security and Eclipse - Java Class Controller

At the opened AuthController.java, add Spring MVC annotation before the class name that tells the Spring application this is a controller.

@Controller
public class AuthController {

}

Declare the Autowired CustomDetailsService field.

    @Autowired
    private CustomUserDetailsService userService;

Create a model and view method for the login page.

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

Create a model and view method for the signup/register page.

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

Create a model and view method for saving the new user when form submitted from the signup page.

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

        }
        return modelAndView;
    }

As usual, press Command + Shift + O to organize imports. So, the whole imports look like this.

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.djamware.mynotes.models.User;
import com.djamware.mynotes.services.CustomUserDetailsService;

Next, create a controller for Notes CRUD using the same way as the previous controller. Give the name "NotesController" and package name "com.djamware.mynotes.controllers". Go to opened NotesController.java then add this Spring MVC Controller annotation.

@Controller
public class NotesController {

}

Add these Autowired and all required CRUD request mapping methods inside the class body.

    @Autowired
    private CustomUserDetailsService userService;
    
    @Autowired
    private NotesRepository noteRepository;
    
    @RequestMapping(value = "/notes", method = RequestMethod.GET)
    public ModelAndView notes() {
        ModelAndView modelAndView = new ModelAndView();
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        User user = userService.findUserByEmail(auth.getName());
        modelAndView.addObject("notes", noteRepository.findAll());
        modelAndView.addObject("currentUser", user);
        modelAndView.addObject("fullName", "Welcome " + user.getFullname());
        modelAndView.addObject("adminMessage", "Content Available Only for Users with Admin Role");
        modelAndView.setViewName("notes");
        return modelAndView;
    }

    @RequestMapping("/notes/create")
    public ModelAndView create() {
        ModelAndView modelAndView = new ModelAndView();
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        User user = userService.findUserByEmail(auth.getName());
        modelAndView.addObject("currentUser", user);
        modelAndView.addObject("fullName", "Welcome " + user.getFullname());
        modelAndView.addObject("adminMessage", "Content Available Only for Users with Admin Role");
        modelAndView.setViewName("create");
        return modelAndView;
    }

    @RequestMapping("/notes/save")
    public String save(@RequestParam String title, @RequestParam String content) {
        Notes note = new Notes();
        note.setTitle(title);
        note.setContent(content);
        note.setUpdated(new Date());
        noteRepository.save(note);

        return "redirect:/notes/show/" + note.getId();
    }

    @RequestMapping("/notes/show/{id}")
    public ModelAndView show(@PathVariable Long id) {
        ModelAndView modelAndView = new ModelAndView();
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        User user = userService.findUserByEmail(auth.getName());
        modelAndView.addObject("currentUser", user);
        modelAndView.addObject("fullName", "Welcome " + user.getFullname());
        modelAndView.addObject("adminMessage", "Content Available Only for Users with Admin Role");
        modelAndView.addObject("note", noteRepository.findById(id).orElse(null));
        modelAndView.setViewName("show");
        return modelAndView;
    }

    @RequestMapping("/notes/delete")
    public String delete(@RequestParam Long id) {
        Notes note = noteRepository.findById(id).orElse(null);
        noteRepository.delete(note);

        return "redirect:/notes";
    }

    @RequestMapping("/notes/edit/{id}")
    public ModelAndView edit(@PathVariable Long id) {
        ModelAndView modelAndView = new ModelAndView();
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        User user = userService.findUserByEmail(auth.getName());
        modelAndView.addObject("currentUser", user);
        modelAndView.addObject("fullName", "Welcome " + user.getFullname());
        modelAndView.addObject("adminMessage", "Content Available Only for Users with Admin Role");
        modelAndView.addObject("note", noteRepository.findById(id).orElse(null));
        modelAndView.setViewName("edit");
        return modelAndView;
    }

    @RequestMapping("/notes/update")
    public String update(@RequestParam Long id, @RequestParam String title, @RequestParam String content) {
        Notes note = noteRepository.findById(id).orElse(null);
        note.setTitle(title);
        note.setContent(content);
        note.setUpdated(new Date());
        noteRepository.save(note);

        return "redirect:/notes/show/" + note.getId();
    }

As usual, press Command + Shift + O to organize the imports. So, the whole of imports will be like this.

import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.djamware.mynotes.models.Notes;
import com.djamware.mynotes.models.User;
import com.djamware.mynotes.repositories.NotesRepository;
import com.djamware.mynotes.services.CustomUserDetailsService;


Step #7: Add a Configuration for Spring MVC and Security

To make the view and controller available in the Spring Boot application, create a new file for a Spring Web MVC Configuration. Right-click the project name in the project explorer pane then click New -> Class. On the new Java Class form, fill Class Name with "PageConfig" and package name "com.djamware.mynotes.config" then click the Finish button.

Create Java Login Web App using Spring Security and Eclipse - Java Class Config

On the opened PageConfig.java, add the Spring MVC annotation to determine the Configuration class. Also, add implements of WebMvcConfigurer to the class name.

@Configuration
public class PageConfig implements WebMvcConfigurer {

}

Add BCryptPasswordEncoder bean inside the class body.

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder;
    }

Add an override method to register the controllers and the views.

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/notes").setViewName("notes");
        registry.addViewController("/").setViewName("notes");
        registry.addViewController("/login").setViewName("login");
    }

Press Command + Shift + O to organize imports. So, the imports should be like this.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

Do the same way to create Spring Web Security configuration using the file name "WebSecurityConfig" inside the same package name as the previous file. After the file opened, add these annotations before the class name and extend after the class name to make the class as a Spring Boot configuration class, enable Spring Web Security and extend Spring Security "WebSecurityConfigurerAdapter".

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

}

Declare the variable for "CustomizeAuthenticationSuccessHandler" class that will created later.

    @Autowired
    CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler;

Declare the variable for "BCryptPasswordEncoder".

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

Create a bean for the Spring Security "UserDetailsService" that use the "CustomUserDetailsService" class.

    @Bean
    public UserDetailsService jpaUserDetails() {
        return new CustomUserDetailsService();
    }

Add an override method for a manage authentication mechanism that uses "UserDetailsService" and Bcrypt password encoder.


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        UserDetailsService userDetailsService = jpaUserDetails();
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);

    }

Add an override method for securing the HTTP requests.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/").hasAuthority("ADMIN").antMatchers("/h2/console").permitAll()
                .antMatchers("/login").permitAll().antMatchers("/signup").permitAll().antMatchers("/notes")
                .hasAuthority("ADMIN").antMatchers("/notes/**").hasAuthority("ADMIN").anyRequest().authenticated().and()
                .csrf().disable().formLogin().successHandler(customizeAuthenticationSuccessHandler).loginPage("/login")
                .failureUrl("/login?error=true").usernameParameter("email").passwordParameter("password").and().logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/").and()
                .exceptionHandling();
    }

That configuration also adds a custom login success handler using the custom class. Next, add an override method to exclude static resources that use in this web application. They're also a custom username parameter that we use "email" as the parameter.

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**");
    }

Press Command + Shift + O to organize imports. So, the whole imports like this.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.djamware.mynotes.services.CustomUserDetailsService;

As you see there is "CustomizeAuthenticationSuccessHandler", we need to create a class for the custom login success handler. Do the same way as the previous step using the file name "CustomizeAuthenticationSuccessHandler" with the same package. After file created and opened, make this class as a Spring component and implements Spring Security `AuthenticationSuccessHandler` by adding this annotation and implements.

@Component
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    
}

Add an override method for the custom landing page after a successful login.

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        // set our response to OK status
        response.setStatus(HttpServletResponse.SC_OK);

        for (GrantedAuthority auth : authentication.getAuthorities()) {
            if ("ADMIN".equals(auth.getAuthority())) {
                response.sendRedirect("/notes");
            }
        }
    }

Press Command + Shift + O to organize the imports. So, the whole imports like this.

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;


Step #8: Create All Required Spring MCV Views with Bootstrap

Before create or edit HTML, Javascript, and CSS files, make sure you have installed Eclipse Web Developer Tools from Help menu -> Eclipse Marketplace. To create HTML views that implement the Thymeleaf library, first, we will use an existing blank folder inside the "resources" folder. Then add these HTML files inside that folder by right-clicking the templates folder then New -> HTML file.

default.html
notes.html
create.html
show.html
edit.html
login.html
signup.html

On the opened "default.html" then replace it with these lines of HTML tags.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <title layout:title-pattern="$CONTENT_TITLE - $LAYOUT_TITLE">My Notes</title>
        <meta name="description" content=""/>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
        <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css" integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ" crossorigin="anonymous">
        <link rel="stylesheet" href="/css/style.css" />
    </head>
    <body>
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
            <a class="navbar-brand" href="/"><i class="fas fa-book"></i> My Notes</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>

            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav mr-auto">
                    <li class="nav-item active">
                        <a class="nav-link" href="/"><i class="fas fa-home"></i> <span class="sr-only">(current)</span></a>
                    </li>
                </ul>
                <ul class="navbar-nav ml-auto">
                    <li class="nav-item active dropdown" th:if="${currentUser}">
                        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" th:text="${fullName}"></a>
                        <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                             <div class="dropdown-item">
                                 <form th:action="@{/logout}" method="post">
                                    <input type="submit" value="Sign Out"/>
                                </form>
                             </div>
                        </div>
                    </li>
                </ul>
            </div>
        </nav>

        <div class="container">
            <div layout:fragment="content"></div>
        </div>
        <!-- /.container -->

        <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    </body>
</html>

As you see, we are using Bootstrap 4 as the responsive frontend, all required CSS and Javascript library loaded from CDN. We put the condition in the top navigation bar for show login and log out button. The other pages put inside "layout:fragment" that will be using the same layout.

Next, change to the opened "notes.html" tab then replace all HTML tags with these.

<!DOCTYPE HTML>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="default">
    <head>
        <title>Home</title>
    </head>
    <body>
        <div layout:fragment="content" class="row">
            <div class="col-xs-8 col-md-8">
                <h3>
                    <a href="/notes/create" class="btn btn-primary"><i class="fas fa-plus-square"></i> Note</a>
                </h3>
                <h2>My Notes</h2>
                <div class="table-responsive">
                    <table class="table" id="notes-table">
                        <thead>
                            <tr>
                                <th>Title</th>
                                <th>Content</th>
                                <th>Updated</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr th:each="note : ${notes}">
                                <td><a th:text="${note.title}" th:href="@{'/notes/show/' + ${note.id}}"></a></td>
                                <td th:text="${note.content}"></td>
                                <td th:text="${new java.text.SimpleDateFormat('dd MMM yyyy').format(note.updated)}"></td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </body>
</html>

Next, change to the opened "create.html" tab then replace all HTML tags with these.

<!DOCTYPE HTML>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="default">
    <head>
        <title>Create Note</title>
    </head>
    <body>
        <div layout:fragment="content" class="row">
            <div class="col-xs-8 col-md-8">
                <h3>
                    <a href="/notes" class="btn btn-lg btn-primary"><i class="fas fa-list"></i> Notes</a>
                </h3>
                <h2>Create Note</h2>
                <form action="/notes/save">
                    <div class="form-group">
                        <label for="title">Title:</label>
                        <input type="text" class="form-control" name="title" />
                    </div>
                    <div class="form-group">
                        <label for="content">Content</label>
                        <textarea class="form-control" name="content" cols="60" rows="3"></textarea>
                    </div>
                    <button type="submit" class="btn btn-success"><i class="fas fa-save"></i> Save</button>
                </form>
            </div>
        </div>
    </body>
</html>

Next, change to the opened "show.html" tab then replace all HTML tags with these.

<!DOCTYPE HTML>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="default">
    <head>
        <title>Show Note</title>
    </head>
    <body>
        <div layout:fragment="content" class="row">
            <div class="col-xs-8 col-md-8">
                <h3>
                    <a href="/notes" class="btn btn-primary"><i class="fas fa-list"></i> Notes</a>
                </h3>
                <h2 th:text="${note.title}"></h2>
                <dl class="list">
                    <dt>Content</dt>
                    <dd th:text="${note.content}"></dd>
                    <dt>Updated</dt>
                    <dd th:text="${new java.text.SimpleDateFormat('dd MMM yyyy').format(note.updated)}"></dd>
                </dl>
                <form action="/notes/delete">
                    <input type="hidden" name="id" th:value="${note.id}" />
                    <h2><input type="submit" class="btn btn-danger" value="Delete" onclick="return confirm('Are you sure?');" />
                        <a th:href="@{'/notes/edit/' + ${note.id}}" class="btn btn-warning"><i class="fas fa-edit"></i> Edit</a></h2>
                </form>
            </div>
        </div>
    </body>
</html>

Next, change to the opened "edit.html" tab then replace all HTML tags with these.

<!DOCTYPE HTML>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="default">
    <head>
        <title>Edit Note</title>
    </head>
    <body>
        <div layout:fragment="content" class="row">
            <div class="col-xs-8 col-md-8">
                <h3>
                    <a href="/notes" class="btn btn-lg btn-primary"><i class="fas fa-list"></i> Notes</a>
                </h3>
                <h2>Edit Note</h2>
                <form action="/notes/update">
                    <div class="form-group">
                        <label for="title">Title:</label>
                        <input type="text" class="form-control" name="title" th:value="${note.title}" />
                    </div>
                    <div class="form-group">
                        <label for="content">Content</label>
                        <textarea class="form-control" name="content" cols="60" rows="3" th:text="${note.content}"></textarea>
                    </div>
                    <input type="hidden" name="id" th:value="${note.id}" />
                    <button type="submit" class="btn btn-success"><i class="fas fa-save"></i> Update</button>
                </form>
            </div>
        </div>
    </body>
</html>

Next, change to the opened "login.html" tab then replace all HTML tags with these.

<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="default">
    <head>
        <title>Login</title>
    </head>
    <body class="text-center">
        <div layout:fragment="content">
            <form class="form-signin" th:action="@{/login}" method="post">
                <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
                <div th:if="${param.error}">
                    Invalid email and password.
                </div>
                <div th:if="${param.logout}">
                    You have been logged out.
                </div>
                <label for="inputEmail" class="sr-only">Email address</label>
                <input type="email" name="email" id="inputEmail" class="form-control" placeholder="Email" required />
                <label for="inputPassword" class="sr-only">Password</label>
                <input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required />
                <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
            </form>
            <form class="form-signin" th:action="@{/signup}" method="get">
                <button class="btn btn-md btn-success btn-block" type="Submit">Signup Here</button>
            </form>
        </div>
    </body>
</html>

Next, change to the opened "signup.html" tab then replace all HTML tags with these.

<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="default">
    <head>
        <title>Signup</title>
    </head>
    <body>
        <div layout:fragment="content">
            <form class="form-signin" th:action="@{/signup}" method="post">
                <h1 class="h3 mb-3 font-weight-normal">Signup Here</h1>
                <div th:if="${param.error}">
                    Invalid email and password.
                </div>
                <div th:if="${param.logout}">
                    You have been logged out.
                </div>
                <label for="inputUsername" class="sr-only">Username</label>
                <input type="email" name="email" id="inputEmail" class="form-control" placeholder="Username" required />
                <label for="inputPassword" class="sr-only">Password</label>
                <input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required />
                <label for="inputFullname" class="sr-only">Full Name</label>
                <input type="text" name="fullname" id="inputEmail" class="form-control" placeholder="Fullname" required />
                <button class="btn btn-lg btn-primary btn-block" type="submit">Sign Up</button>
            </form>
            <form class="form-signin" th:action="@{/login}" method="get">
                <button class="btn btn-md btn-success btn-block" type="Submit">Sign In</button>
            </form>
        </div>
    </body>
</html>

Next, add CSS file for custom styling by right-clicking the resources -> static then click New -> Folder. Give it name "css" then click the Finish button. Add a CSS file by right-clicking that folder then click New -> Other -> Web -> CSS File. Give it the name "style.css". On the opened "style.css", replace all CSS codes with these.

html,
body {
  height: 100%;
}

body {
  display: -ms-flexbox;
  display: flex;
  -ms-flex-align: center;
  align-items: center;
  padding-top: 40px;
  padding-bottom: 40px;
  background-color: #f5f5f5;
}

.form-signin {
  width: 100%;
  max-width: 330px;
  padding: 15px;
  margin: auto;
}
.form-signin .checkbox {
  font-weight: 400;
}
.form-signin .form-control {
  position: relative;
  box-sizing: border-box;
  height: auto;
  padding: 10px;
  font-size: 16px;
}
.form-signin .form-control:focus {
  z-index: 2;
}
.form-signin input[type="email"] {
  margin-bottom: -1px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.form-signin input[type="text"] {
  margin-bottom: 10px;
}


Step #9: Run and Test the Java Web App

Before run and test this Spring Boot Security Java login web app, we need to add the initial Role data. For that, open and edit "src/main/java/com/djamware/mynotes/MynotesApplication.java" then add this bean after the main method.

    @Bean
    CommandLineRunner init(RoleRepository roleRepository) {

        return args -> {

            Role adminRole = roleRepository.findByRole("ADMIN");
            if (adminRole == null) {
                Role newAdminRole = new Role();
                newAdminRole.setRole("ADMIN");
                roleRepository.save(newAdminRole);
            }

            Role userRole = roleRepository.findByRole("USER");
            if (userRole == null) {
                Role newUserRole = new Role();
                newUserRole.setRole("USER");
                roleRepository.save(newUserRole);
            }
        };

    }

Next, run this Java web app by right-clicking the project name in the project explorer then Run As -> Spring Boot App. If there's an error like this in when you log in.

org.springframework.security.authentication.InternalAuthenticationServiceException: failed to lazily initialize a collection of role: com.djamware.mynotes.models.User.roles, could not initialize proxy - no Session

Open and edit "src/main/resources/application.properties" then add this line to enable Hibernate lazy load if no transactional.

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

Re-run again the Spring boot Java web app, then you will see the working Spring Security login app like this.

Spring Boot Tutorial: Create Java Login Web App using Spring Security and Eclipse - Login Page
Spring Boot Tutorial: Create Java Login Web App using Spring Security and Eclipse - Signup Page
Spring Boot Tutorial: Create Java Login Web App using Spring Security and Eclipse - Home Page
Spring Boot Tutorial: Create Java Login Web App using Spring Security and Eclipse - Create Page
Spring Boot Tutorial: Create Java Login Web App using Spring Security and Eclipse - Show Page
Spring Boot Tutorial: Create Java Login Web App using Spring Security and Eclipse - List Page

That it's, the Create Java Login Web App using Spring Security and Eclipse. You can get the full source code from our GitHub.

Thanks!

Loading…