Create Java Login Web App using Spring Security and Eclipse

Create Java Login Web App using Spring Security and Eclipse

In this Spring Boot 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.

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:

  • Step #1: Prepare Eclipse and Spring Tools
  • Step #2: Generate Spring Boot Java Web App
  • Step #3: Create Java Notes, User and Role Model Class
  • Step #4: Create Java Notes, User and Role Repository Interface
  • Step #5: Create a Custom Java User Details Service
  • Step #6: Create Controller for All Spring MVC Views
  • Step #7: Add a Configuration for Spring MVC and Security
  • Step #8: Create All Required Spring MCV Views with Bootstrap
  • Step #9: Run and Test the Java Web App

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.

Step #1: Prepare Eclipse and Spring Tools

Download Eclipse installer from their official site.

https://s3-ap-southeast-1.amazonaws.com/djamblog/article-300120061932.png

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

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.

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

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.

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.

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

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.

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

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.

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

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.

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.

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.

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.






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!

Set up Web App with Spring Boot and Spring Security

Set up Web App with Spring Boot and Spring Security

Download the Spring Boot Web App Example Project. Run the Initial Web App. Add Project Dependencies for Your Spring Boot + Spring Security Web App. Understand Your Spring Boot App. Set Up Okta for OAuth 2.0 Single Sign-On. Configure Your Spring Boot App for Single SignOn (SSO) Refine Our Permissions

Developers know that securing web apps can be a pain. Doing it right is tough. The worst part is that “right” is a moving target. Security protocols change. Vulnerabilities are found in dependencies and patches are released. Tons of often complex boilerplate code has to be generated. The software-as-service paradigm has proliferated over the last decade, and while I love reinventing the wheel as much as the next developer (because, clearly, I’m gonna write it better than the yahoo they hired), security is an area where I’m happy to offload this work to specialists. Enter Okta.

In this tutorial, you’re going to use Spring Boot to build a simple web application with a user registration system and a login system. It will have the following features:

  • Login and registration pages
  • Password reset workflows
  • Restricting access according to group membership
Download the Spring Boot Web App Example Project

The first thing you’re going to need is a free Okta account. If you don’t already have one

The next thing will be to download the example project for this tutorial from GitHub.

git clone https://github.com/oktadeveloper/okta-spring-simple-app-example.git spring-app

This project uses Gradle, as the build tool, and the Thymeleaf templating system.

Run the Initial Web App

Once you have downloaded the example code from the GitHub repository, checkout out the Start tag using the following git command: git checkout tags/Start.

The app at this point it not protected at all. There is no authorization or authentication enabled (even though the necessary dependencies are included in the build.gradle file). Go ahead and run the example by opening a terminal and, from the project root directory, running the command ./gradlew bootRun (The bootRun command is a task provided by the Gradle Spring Boot plugin, added to the build.gradle file in the buildscript section at the top of the file).

Navigate to http://localhost:8080 in your favorite browser, and you should see this:

And if you click on the “Restricted” button:

Add Project Dependencies for Your Spring Boot + Spring Security Web App

The project dependencies are defined in the build.gradle file (see below). There’s a lot going on in this file, and this tutorial isn’t going to try and explain the Gradle build system to you. Feel free to check out their documentation. I just want to point out a few things.

First off, notice that we’re including the okta-spring-boot-starter. This project greatly simplifies integrating Okta with your Spring Boot application. It’s entirely possible to use Okta and Spring Boot without this starter. In fact, up to the point where Groups and Roles are introduced, the differences are minor (mostly involve application.yml changes). However, once you start to trying to integrate Groups and Roles, the Okta Spring Boot Starter saves a lot of coding. If you’d like to look a little deeper, take a look at the Okta Spring Boot Starter GitHub project.

The rest of the dependencies deal with Spring and Spring Boot. You’ll notice none of the org.springframework.bootdependencies have version numbers. This is because of some behind-the-scenes magic being done by the Spring io.spring.dependency-management Gradle plugin. The Spring Boot Version is set by the build script property springBootVersion near the top of the build.gradle file. Based on this version number, the Spring dependency management plugin decides what versions of dependencies to include.

We’re also bringing in the org.springframework.boot Gradle plugin, which adds the bootRun task that we’ll use to run the app.

  • spring-boot-starter-security and spring-boot-starter-web are core Spring Boot dependencies.
  • spring-security-oauth2-autoconfigure is required to use the @EnableOAuth2Sso annotation that we use to hook OAuth and Single Sign-On into our app.
  • spring-boot-starter-thymeleaf and thymeleaf-extras-springsecurity4 bring in the Thymeleaf templating system and integrate it with Spring Security.
buildscript {  
   ext {  
      springBootVersion = '2.0.5.RELEASE'  
  }  
   repositories {  
      mavenCentral()  
   }  
   dependencies {  
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")  
   }  
}  

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.okta.springboot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
compile('com.okta.spring:okta-spring-boot-starter:0.6.0')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4')
compile('org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.0.5.RELEASE')
testCompile('org.springframework.boot:spring-boot-starter-test') "
testCompile('org.springframework.security:spring-security-test')
}

/*
This is required to resolve a logging dependency conflict between the
okta-spring-boot-starter and the various spring dependencies.
*/
configurations.all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
exclude group: 'org.springframework.boot', module: 'logback-classic'
}

Understand Your Spring Boot App

The Java web application has only three class files and a few templates. Obviously Spring Boot is doing a lot of heavy hitting going on in the background, but what’s going on in our class files?

The application entry point is in the SpringSimpleApplication class:

@SpringBootApplication
public class SpringSimpleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSimpleApplication.class, args);
}
}

Two important things are happening here that get things rolling: 1) we use the @SpringBootApplication annotation, and 2) our main method calls the SpringApplication.run() method. This is the entry point to the entire Spring/Spring Boot system.

The SpringSecurityWebAppConfig class is a way to use Java code to configure how Spring Boot handles web app security. Here we use the HttpSecurity object to remove authorization from all endpoints. By default, the Spring Boot behavior is the opposite: all endpoints require authorization.

@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {

@Override  
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().permitAll();          
}

}

The @Configuration annotation tells Spring that we are using the class as a source of programmatic configuration, allowing us to override the configure() method.

The last Java class, SimpleAppController, is our only controller object. Controllers in a Spring Boot web application are where URL requests are mapped to Java code. The @Controller annotation tells Spring that this class is a controller.

@Controller
class SimpleAppController {

@RequestMapping("/")  
String home() {  
    return "home";  
}  

@RequestMapping("/restricted")  
String restricted() {  
    return "restricted";  
}  

}

Connections between class methods and URLs are made using the @RequestMapping annotation.

We have two mappings:

  1. “home” mapping
  2. “restricted” mapping

Remember that initially nothing is actually “restricted”, so don’t get confused by that. You’ll lock that mapping down in a bit.

Also notice that the classes return a simple text string, but this is getting auto-magically turned into a full html file. This is part of the Thymeleaf dependency that is included in the build.gradle file. These strings are assumed to be template file names, which are by default paths in the templates directory on the classpath.

Thus “home” is mapped to the src/main/resources/templates/home.html template file. When the web app is packaged in the the final jar, the entire resources folder is copied into the classpath, so that the templates directory is accessible at runtime.

Set Up Okta for OAuth 2.0 Single Sign-On

Now you’re going to set up authorization for our app. Okta makes this super easy. You should have already signed up for a free developer.okta.com account. Now you’re going to create an OpenID Connect (OIDC) application to use with OAuth 2.0 Single Sign-On (SSO).

That might be a lot of jargon and acronyms, if you’re not already familiar with them. Very simply, OAuth 2.0 is an industry standard for authorization - a standardized and tested method by which authorization servers and applications can communicate to facilitate user authorization. OpenID Connect is a layer on top of OAuth 2.0 that standardizes and simplifies the authorization procedure as well as providing user authentication. Together they provide a proven way for an application to interact with a remote server that provides authentication and authorization services (such as Okta).

To create an OIDC app, open your Okta developer dashboard. Click on the Applications top menu item, and then click on Add Application.

You should see the following screen. Click on the icon for the Web option. Click Next.

You need to update a few of the initial configuration options. First change the name to something more descriptive. I used “Okta Spring Boot Simple Web App.” Next update the Login redirect URIs to http://localhost:8080/login. Click Done.

This will take you to the new application’s general configuration tab. Scroll down and note the Client ID and Client secret. You’ll need these later.

That’s all you need to do to set up Okta for OAuth! Now let’s return to the Spring Boot app and hook our new OIDC application into the Spring Boot application.

Configure Your Spring Boot App for Single Sign-On (SSO)

Now you need to configure the Spring Boot app to interact with the Okta servers. This is super easy. We need to do two things:

  1. Add the @EnableOAuth2Sso annotation
  2. Update the application.yml configuration

First add the @EnableOAuth2Sso annotation to the SpringSecurityWebAppConfig class.

@EnableOAuth2Sso
@Configuration
public class WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

@Override  
protected void configure(HttpSecurity http) throws Exception {  
    http.authorizeRequests().anyRequest().permitAll();          
}  

}

The @EnableOAuth2Sso annotation does a TON of stuff. It’s worth digging into to understand what’s going on. You can check out Spring’s docs on the annotation itself, and their Spring Boot and OAuth2 tutorial.

One thing I want to point out (bc this has been bugging me a while and I just figured it out) is that you can put this annotation on other classes in the project. However, if you do, be aware that Spring is going to create a WebSecurityConfigurerAdapter and add it to the security chain. Since we’re also creating a WebSecurityConfigurerAdapter, there will be two of them, and you’ll get an error about conflicting chain orders. This is because both WebSecurityConfigurerAdapters will by default use the same chain order. You can resolve this error by adding an @Order(101) annotation to our customized class. However, even better is to add the @EnableOAuth2Sso annotation to our WebSecurityConfigurerAdapter class, WebSecurityConfigurerAdapter, and Spring will use that class instead of creating a duplicate one.

The second change you need to make is update the src/main/resources/application.yml file, filling in some Okta-specific configuration options for the OAuth SSO values take from our Okta OIDC application.

You’ll need to fill in your Client ID and Client secret from the application you created above. You’ll also need to change the issuer URL so that it reflects your Okta preview URL, something like dev-123456.oktapreview.com.

server:
port: 8080

spring:
resources: static-locations: "classpath:/static/"

okta:
oauth2:
issuer: https://{yourOktaDomain}/oauth2/default
clientId: {yourClientId}
clientSecret: {yourClientSecret}
rolesClaim: groups

Refine Our Permissions

Now you’re going to want to update the SpringSecurityWebAppConfig class so that you have a public home page and a restricted “restricted” page. We do this by using Spring’s fluent API for the HttpSecurity object.

import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableOAuth2Sso
@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {

@Override  
protected void configure(HttpSecurity http) throws Exception {  
    http.authorizeRequests()  
            .antMatchers("/").permitAll() // allow all at home page
            .antMatchers("/img/**").permitAll()  // allow all to access static images
            .anyRequest().authenticated();  // authenticate everything else!
}  

}

Restart your app and now you should be able to:

  1. See the home page without authenticating
  2. NOT see the /restricted page without authenticating
  3. Be able to authenticate using Okta Single Sign-On

This point in the tutorial corresponds to the OktaOAuthSSO tag in the GitHub repository.

Take a Look at the Thymeleaf Templates

The Thymeleaf templates are pretty self explanatory, on the whole, but I did want to point out a couple things. Thymeleaf templates are fully valid HTML5, which is nice. If you want to dig deeper, you can head over to their website and their documentation.

What I wanted to point out is how the template brings in authentication information. To do this, we’re using the thymeleaf-extras-springsecurity plugin. This is included in the build.gradle file with the following line:

compile ("org.thymeleaf.extras:thymeleaf-extras-springsecurity4")

And is included in the template file as an XML namespace attribute on the main <html> tag.

xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"

This plugin is what allows us to check if a user is authenticated using the th:if attribute with a custom SPEL expression (Spring Expression Language). It also allows us to insert authentication properties. Below you see a span <span th:text="${#authentication.name}"></span> that is used to insert the name of the authenticated user.

<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<!--// <th:block th:include="fragments/head :: head"/> //-->
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="box col-md-6 col-md-offset-3">
<div class="okta-header">
<img src="img/logo.png"/>
</div>

        &lt;!--/* displayed if account IS NOT null, indicating that the user IS logged in */--&gt;  
        &lt;div th:if="${#authorization.expression('isAuthenticated()')}"&gt;  
            &lt;h1 th:inline="text"&gt;Hello, &lt;span th:text="${#authentication.name}"&gt;&lt;/span&gt;!&lt;/h1&gt;  
            &lt;a href="/restricted" class="btn btn-primary"&gt;Restricted&lt;/a&gt;  
        &lt;/div&gt;  

        &lt;!--/* displayed if account IS null, indicating that the user IS NOT logged in */--&gt;  
        &lt;div th:unless="${#authorization.expression('isAuthenticated()')}"&gt;  
            &lt;h1&gt;Who are you?&lt;/h1&gt;  
            &lt;a href="/restricted" class="btn btn-primary"&gt;Restricted&lt;/a&gt;  
        &lt;/div&gt;  
    &lt;/div&gt;  
&lt;/div&gt;  

</div>
</body>
</html>

The thymeleaf-extras-springsecurity plugin has some other nice features as well. If you want to dig a little deeper, check out the project repository on GitHub.

Secure Access By Group Membership

The next step in our tutorial is to add Group-based authentication using user groups that we’ll create and define on Okta. A very common example of this is to have an “admin” section of a website and a “user” section of a website, along with perhaps a public home page open to everybody. In this example, “admin” and “user” would correspond to two different groups of which an authenticated user could be a member. What we want to do is be able to restrict access to URL endpoints based on user group membership, and to be able to assign users to these groups.

A side note: groups vs roles. What’s the difference?

  • A “group” is a collection of users, and permissions are assigned to the group. Generally speaking group membership is relatively static, at least throughout the duration of a session.
  • A “role” is a set of permissions that a user can inherit when he/she acts under that role. Roles are generally more dynamic in nature. Users can have many roles. Roles frequently are activated or deactivated depending on complex criteria and often may change throughout a user session.

In practice, for simple authorization systems, they’re pretty similar. The main difference is that groups classify based on individual identity, whereas roles classify based on permissible activities. You’ll probably see apps and tutorials on the wild and woolly internet that ignore this difference, as it’s functionally somewhat subtle. (But now you know. And you can get on the comment thread for the tutorial in question and write a comment correcting the author.)

Configure Authorization Groups in Okta

Go to your developer.okta.com dashboard. From the top menu, go to Users and click on Groups.

Click on the Add Group button.

Name the group “Admin” and give it a description (I put “Administrators,” doesn’t matter what you put here really, just something descriptive).

Click on the group Name to open the group and click on the Add Members button. Add your user to the Admin group.

Next add a new user that’s not an admin.

  • Go to Users from the top menu and click on People.
  • Click Add Person.
  • Fill out the popup form:
  • First name: Not
  • Last name: Admin
  • Username: [email protected]
  • No groups or secondary email
  • Password: Set by admin
  • Assign a password
  • Uncheck “User must change password on first login”
  • Click Save

The next thing you’ll need to do is add a “groups” claim to the default authorization server.

  • From the top menu, go to API and click on Authorization Servers”
  • Click on the default authorization server.
  • Click on the Claims tab.
  • Click the Add Claim button.
  • Update the popup form to match the image below
  • Name: groups
  • Token type: Access
  • Value type: Groups
  • Filter: Regex .*
  • Don’t disable
  • Include in any scope

What you’re doing here is telling Okta to include a “groups” claim in the access token that is sent to your application. This is the OAuth method of Okta telling your application about the groups your authenticated user is a member of. Somewhat confusingly, these will be called “authorities” on the Spring application side, which is an abstract term for groups/roles/privileges communicated by the OAuth server to the app.

Now we have two users. Your primary user, which has been added to the Admin group, and a new user that is not in the admin group. We’ve also configured Okta to add the groups claim to the access token. Now all we have to do is make a few changes to the app code!

Update Your Spring Boot + Spring Security App to Use Group-based Authorization

This is where the Okta Spring Boot Starter really starts to shine. Normally if you wanted to map the security groups and groups claims that we are sending in the token to groups in the app, you’d have to write an extractor class or two to handle the extraction, as well as perhaps a group class. The Okta Spring Boot Starter handles all of this for you!

The first thing you’re going to want to do is add the following annotation to your SpringSecurityWebAppConfig class.

@EnableGlobalMethodSecurity(prePostEnabled = true)

Like so:

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@EnableOAuth2Sso
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
/* class contents omitted for brevity */
}

This annotation enables the next annotation that we’re going to use, the @PreAuthorize annotation. This annotation allows us to use a Spring Expression Language (SpEL) predicate to determine if the controller method is authorized. The predicate expression is executed before the app even enters the controller method (hence the “pre”-authorize).

In the SimpleAppController class, add a new method called admin like so:

import org.springframework.security.access.prepost.PreAuthorize;

@Controller
class SimpleAppController {

/* other controllers omitted for clarity */ 

@RequestMapping("/admin")  
@PreAuthorize("hasAuthority('Admin')")  
String admin() {  
    return "admin";  
}  

}

Just to recap a little, this method does the following:

  • create a mapping for the /admin url endpoint;
  • assign the /admin endpoint an authorization scheme based on SpEL;
  • and simply return the name of a Thymeleaf template, assumed to be in the /templates directory (which we’ll create next).

Create the new admin template page. In the src/main/resources/templates directory, create a new file called admin.html with the following contents:

<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<!--// <th:block th:include="fragments/head :: head"/> //-->
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="box col-md-6 col-md-offset-3">
<div class="okta-header">
<img src="img/logo.png"/>
</div>

        &lt;h1&gt;Welcome to the admin page!&lt;/h1&gt;  

        &lt;a href="/" class="btn btn-primary"&gt;Go Home&lt;/a&gt;  

    &lt;/div&gt;  
&lt;/div&gt;  

</div>
</body>
</html>

You may be asking yourself what the SpEL expression used in the @PreAuthorize annotation means. Why is the SpEL expression hasAuthority and not hasGroup? A correct answer is somewhat complicated, having to do with the fact that Spring calls permissions privileges and authorities in different contexts, which can be mapped to groups and roles in the app. When using Spring Boot and OAuth, an ‘authority’ is often equated with a ‘role’, which is fine. But you said we’re using groups, not roles? Right. Practically speaking, in this instance, it doesn’t matter because Okta knows we’re talking about groups and the app knows we’re talking about groups, and in the middle we just use the groups claim and the authorities fields to communicate the text strings that represent the groups the user is a member of.

A helpful hint:

If you want to inspect the authentication information that the Spring Boot App is receiving, you can add the following line in one of the controller methods before the return statement.

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

Set a breakpoint on this line, or right after it, really, and run the app with a debugger that allows you to inspect the authentication object. It’s a great way to learn and debug problems.

Try Out Your New Spring Boot + Spring Security Web App!

That’s pretty much it. You should be able to restart the app and log in with two different users. Only the user that was added to the Admin group should be able to access the admin page. You’ll have to directly navigate to http://localhost:8080/admin (as we didn’t add a link or a button). If you try to navigate to the admin page with the other user, you’ll see the beautiful whitelabel error page showing a 403 / Unauthorized error.

Keep in mind that when switching between users you’ll have to stop the app, log out of your developer.okta.com account, and restart the app. You can also use an incognito window in your browser.

This part of the tutorial corresponds to the GroupsAuth tag, which you can checkout using the following command git checkout tags/GroupsAuth.

Thanks for reading. If you liked this post, share it with all of your programming buddies!

Further reading

☞ Spring & Hibernate for Beginners (includes Spring Boot)

☞ Spring Framework Master Class - Learn Spring the Modern Way!

☞ Master Microservices with Spring Boot and Spring Cloud

☞ Spring Boot and OAuth2: Getting the Authorization Code

☞ An Introduction to Spring Boot

☞ How to build GraphQL APIs with Kotlin, Spring Boot, and MongoDB?

☞ Build a Rest API with Spring Boot using MySQL and JPA

☞ Angular 8 + Spring Boot 2.2: Build a CRUD App Today!

☞ Spring Boot vs. Spring MVC vs. Spring: How Do They Compare?

☞ Top 4 Spring Annotations for Java Developer in 2019


Originally published on developer.okta.com

Spring Boot Tutorials - Spring Boot Full Course

Spring Boot Tutorials - Spring Boot Full Course

Spring Boot Tutorials | Full Course - What is Spring? Spring Boot is an open source Java-based framework used to create a Micro Service. Spring Boot contains a comprehensive infrastructure support for developing a micro service and enables you to develop enterprise-ready applications that you can “just run”.

  1. What is Spring ? – 00:05
  2. Dependency Injection? – 05:34
  3. Spring Tool Suite | Spring Boot IDE – 13:40
  4. Spring, Autowire, Dependency Injection – 22:17
  5. Web App using Spring Boot – 40:39
  6. Application Properties File – 55:33
  7. Accepting Client Data – 01:00:11
  8. ModelAndView – 01:08:51
  9. Model Object – 01:16:12
  10. JPA | MVC | H2 Example – 01:20:29
  11. JPA | MVC | H2 Example Part 2 – 01:35:29
  12. Data JPA | MVC | H2 | Query Methods Example – 01:45:12
  13. Data JPA | MVC | H2 | REST Example – 01:54:43
  14. Data JPA | MVC | H2 | REST Example – 02:02:22
  15. Postman | Data JPA | MVC | H2 | REST Example – 02:06:55
  16. Content Negotiation | Data JPA | MVC | H2 | REST – 02:11:29
  17. Spring Boot | MVC | REST Post Example – 2:19:36
  18. Spring Boot | MVC | REST PUT DELETE Example – 02:27:35
  19. Spring Boot Data REST Example – 02:36:30

Spring Boot Tutorials | Full Course:- https://github.com/navinreddy20/Spring-Boot

Build Web App Authentication using Spring Boot, Spring Security, MongoDB and Angular 8

Build Web App Authentication using Spring Boot, Spring Security, MongoDB and Angular 8

In this article, you'll learn how to build web application authentication using Spring Boot, Spring Security, MongoDB, and Angular 8

In this tutorial, we have to build Spring Boot, Spring Security Core, and MongoDB RESTful Authentication as the backend. The Angular 8 used as the frontend using the HttpClient, HttpInterceptor, and RouteGuard modules. The secure page guarded by Angular 8 Route Guard and the secure RESTful API guarded by Spring Security REST. This should be a great combination of a secure modern web application.

The flow of the web application looks like below Sequence Diagram.

The Angular 8 application starts with the secure and route guarded page list of products without authorization headers. The Angular 8 redirect to the Login page. After login with the valid credential, the Angular 8 got the JWT token that validates with Route guard also send together with Authorization headers bearer. In the products list page, the Angular 8 application request products API to Spring Boot Security API include authorization headers. Finally, the Angular 8 page displaying the list of products.

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

We assume that you already installed all required software, tools, and frameworks. So, we will not cover how to install that software, tools, and frameworks. Notes: We are using Spring Boot 2.1.6 (Stable version), JDK 1.8.0_92, and Angular 8.0.6.

Generate a New Spring Boot Gradle Project

To create or generate a new Spring Boot Application or Project, simply go to Spring Initializer. Fill all required fields as below then click on Generate Project button.

The project will automatically be downloaded as a Zip file. Next, extract the zipped project to your java projects folder. On the project folder root, you will find build.gradle file for register dependencies, initially it looks like this.

plugins {

    id 'org.springframework.boot' version '2.1.6.RELEASE'

    id 'java'

    id 'war'

}

 

apply plugin: 'io.spring.dependency-management'

 

group = 'com.djamware'

version = '0.0.1-SNAPSHOT'

sourceCompatibility = '1.8'

 

repositories {

    mavenCentral()

}

 

dependencies {

    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'

    implementation 'org.springframework.boot:spring-boot-starter-security'

    implementation 'org.springframework.boot:spring-boot-starter-web'

    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    testImplementation 'org.springframework.security:spring-security-test'

}

Now, you can work with the source code of this Spring Boot Project using your own IDE or Text Editor. We are using Visual Studio Code (VSCode). In the terminal or command line go to the extracted project folder then run the command to open VSCode.

cd spring-angular-auth 
code .

Next, we have to add the JWT library to the build.gradle as the dependency. Open and edit build.gradle then add this line to dependencies after other implementation.

implementation 'io.jsonwebtoken:jjwt:0.9.1'

Next, compile the Gradle Project by type this command from Terminal or CMD.

gradle compileJava

Next, open and edit src/main/resources/application.properties then add these lines.

spring.data.mongodb.database=springmongodb 
spring.data.mongodb.host=localhost 
spring.data.mongodb.port=27017

Create Product, User and Role Model or Entity Classes

We will be creating all required models or entities for products, user and role. Create a new folder and files inside that new folder.

mkdir src/main/java/com/djamware/springangularauth/models touch src/main/java/com/djamware/springangularauth/models/Products.java touch src/main/java/com/djamware/springangularauth/models/User.java touch src/main/java/com/djamware/springangularauth/models/Role.java

Next, open and edit src/main/java/com/djamware/springangularauth/models/Products.java then add these package and imports.

package com.djamware.springangularauth.models; 

import org.springframework.data.annotation.Id; 
import org.springframework.data.mongodb.core.mapping.Document;

Next, add these lines of codes for class, fields, document annotation that map to MongoDB collection, constructors, and getter/setter for each field.

@Document(collection = "products")
public class Products {
 
    @Id
    String id;
    String prodName;
    String prodDesc;
    Double prodPrice;
    String prodImage;
 
    public Products() {
    }
 
    public Products(String prodName, String prodDesc, Double prodPrice, String prodImage) {
        super();
        this.prodName = prodName;
        this.prodDesc = prodDesc;
        this.prodPrice = prodPrice;
        this.prodImage = prodImage;
    }
 
    public String getId() {
        return id;
    }
 
    public void setId(String id) {
        this.id = id;
    }
 
    public String getProdName() {
        return prodName;
    }
 
    public void setProdName(String prodName) {
        this.prodName = prodName;
    }
 
    public String getProdDesc() {
        return prodDesc;
    }
 
    public void setProdDesc(String prodDesc) {
        this.prodDesc = prodDesc;
    }
 
    public Double getProdPrice() {
        return prodPrice;
    }
 
    public void setProdPrice(Double prodPrice) {
        this.prodPrice = prodPrice;
    }
 
    public String getProdImage() {
        return prodImage;
    }
 
    public void setProdImage(String prodImage) {
        this.prodImage = prodImage;
    }
 
}

Next, open and edit src/main/java/com/djamware/springangularauth/models/User.java then add these package and imports.

package com.djamware.springangularauth.models;
 
import java.util.Set;
 
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.IndexDirection;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

Add these lines of Java codes that contain document annotation that map to the MongoDB collection, class name, fields, constructors, and getter/setter of the fields.

@Document(collection = "users")
public class User {
 
    @Id
    private String id;
    @Indexed(unique = true, direction = IndexDirection.DESCENDING, dropDups = true)
    private String email;
    private String password;
    private String fullname;
    private boolean enabled;
    @DBRef
    private Set<Role> roles;
 
    public String getId() {
        return id;
    }
    public void setId(String 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, open and edit src/main/java/com/djamware/springangularauth/models/Role.java then add these package name and imports.

package com.djamware.springangularauth.models;
 
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.IndexDirection;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

Add these lines of codes that contain document annotation that map to MongoDB collection, class name, fields/variables, constructors, and getter/setter for each field/variables.

@Document(collection = "users")
public class User {
 
    @Id
    private String id;
    @Indexed(unique = true, direction = IndexDirection.DESCENDING, dropDups = true)
    private String email;
    private String password;
    private String fullname;
    private boolean enabled;
    @DBRef
    private Set<Role> roles;
 
    public String getId() {
        return id;
    }
    public void setId(String 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;
    }
 
}

If you want easier Java class creation, auto import organization, generate constructor, and generate getter/setter then you can use IDE like Eclipse, Netbeans, Spring Tool-Suite, or IntelliJ IDEA. So, you don't have to type all codes manually.

Create Product, User and Role Repository Interfaces

Next steps to create Product, User, and Role Repository Interfaces. Create a new folder and files for the repositories.

mkdir src/main/java/com/djamware/springangularauth/repositories
touch src/main/java/com/djamware/springangularauth/repositories/ProductRepository.java
touch src/main/java/com/djamware/springangularauth/repositories/UserRepository.java
touch src/main/java/com/djamware/springangularauth/repositories/RoleRepository.java

Next, open and edit src/main/java/com/djamware/springangularauth/repositories/ProductRepository.java then add these package name and imports.

package com.djamware.springangularauth.repositories;
 
import com.djamware.springangularauth.models.Products;
import org.springframework.data.mongodb.repository.MongoRepository;

Add these lines of codes that contain Java interface name that extends MongoRepository and deletes method.

public interface ProductRepository extends MongoRepository<Products, String> {
 
    @Override
    void delete(Products deleted);
}

Next, open and edit src/main/java/com/djamware/springangularauth/repositories/UserRepository.java then add these package name and imports.

package com.djamware.springangularauth.repositories; 

import com.djamware.springangularauth.models.User; 
import org.springframework.data.mongodb.repository.MongoRepository;

Add these lines of codes that contain Java interface name that extends MongoRepository and find by email method.

public interface UserRepository extends MongoRepository<User, String> {
 
    User findByEmail(String email);
}

Next, open and edit src/main/java/com/djamware/springangularauth/repositories/RoleRepository.java then add these package name and imports.

package com.djamware.springangularauth.repositories; 

import org.springframework.data.mongodb.repository.MongoRepository; 
import com.djamware.springangularauth.models.Role;

Add these lines of codes that contain Java interface name that extends MongoRepository and find by role method.

public interface RoleRepository extends MongoRepository<Role, String> {
 
    Role findByRole(String role);
}

Create a Custom User Details Service

To implements authentication using existing User and Role from MongoDB, we have to create a custom user details service. Create a new folder and Java file for the service.

mkdir src/main/java/com/djamware/springangularauth/services 
touch src/main/java/com/djamware/springangularauth/services/CustomUserDetailsService.java

Next, open and edit src/main/java/com/djamware/springangularauth/services/CustomUserDetailsService.java then add these package name and imports.

package com.djamware.springangularauth.services;
 
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.password.PasswordEncoder;
import org.springframework.stereotype.Service;
 
import com.djamware.springangularauth.models.Role;
import com.djamware.springangularauth.models.User;
import com.djamware.springangularauth.repositories.RoleRepository;
import com.djamware.springangularauth.repositories.UserRepository;

Add these lines of codes that contains service annotation, the class name that implements UserDetailsService, required variables, constructors, and methods for the authentication process.

@Service
public class CustomUserDetailsService implements UserDetailsService {
 
    @Autowired
    private UserRepository userRepository;
 
    @Autowired
    private RoleRepository roleRepository;
 
    @Autowired
    private PasswordEncoder bCryptPasswordEncoder;
 
    public User findUserByEmail(String email) {
        return userRepository.findByEmail(email);
    }
 
    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);
    }
 
    @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");
        }
    }
 
    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;
    }
 
    private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
        return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
    }
}

Configure Spring Boot Security Rest

Now, the main purpose of this tutorial is configuring Spring Security Rest. First, we have to create a bean for JWT token generation and validation. Create a new folder and file for the configuration.

mkdir src/main/java/com/djamware/springangularauth/configs
touch src/main/java/com/djamware/springangularauth/configs/JwtTokenProvider.java
touch src/main/java/com/djamware/springangularauth/configs/JwtConfigurer.java
touch src/main/java/com/djamware/springangularauth/configs/JwtTokenFilter.java
touch src/main/java/com/djamware/springangularauth/configs/WebSecurityConfig.java

Next, open and edit src/main/java/com/djamware/springangularauth/configs/JwtTokenProvider.java then add these package name and imports.

package com.djamware.springangularauth.configs;
 
import java.util.Base64;
import java.util.Date;
import java.util.Set;
 
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
 
import com.djamware.springangularauth.models.Role;
import com.djamware.springangularauth.services.CustomUserDetailsService;
 
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

Next, add these lines of codes that contain component annotation, class name, variables, and methods.

@Component
public class JwtTokenProvider {
 
      @Value("${security.jwt.token.secret-key:secret}")
    private String secretKey = "secret";
 
    @Value("${security.jwt.token.expire-length:3600000}")
    private long validityInMilliseconds = 3600000; // 1h
 
    @Autowired
    private CustomUserDetailsService userDetailsService;
 
    @PostConstruct
    protected void init() {
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
    }
 
    public String createToken(String username, Set<Role> set) {
        Claims claims = Jwts.claims().setSubject(username);
        claims.put("roles", set);
        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);
        return Jwts.builder()//
            .setClaims(claims)//
            .setIssuedAt(now)//
            .setExpiration(validity)//
            .signWith(SignatureAlgorithm.HS256, secretKey)//
            .compact();
    }
 
    public Authentication getAuthentication(String token) {
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }
 
    public String getUsername(String token) {
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
    }
 
    public String resolveToken(HttpServletRequest req) {
        String bearerToken = req.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7, bearerToken.length());
        }
        return null;
    }
 
    public boolean validateToken(String token) {
        try {
            Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            if (claims.getBody().getExpiration().before(new Date())) {
                return false;
            }
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            throw new JwtException("Expired or invalid JWT token");
        }
    }
}

Next, open and edit src/main/java/com/djamware/springangularauth/configs/JwtTokenFilter.java then add these package name and imports.

package com.djamware.springangularauth.configs;
 
import java.io.IOException;
 
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

Next, add these lines of codes that contain component annotation, class name, variables, and methods.

public class JwtTokenFilter extends GenericFilterBean {
 
    private JwtTokenProvider jwtTokenProvider;
 
    public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }
 
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
        throws IOException, ServletException {
        String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
        if (token != null && jwtTokenProvider.validateToken(token)) {
            Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null;
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        filterChain.doFilter(req, res);
    }
}

Next, open and edit src/main/java/com/djamware/springangularauth/configs/JwtConfigurer.java then add these package name and imports.

package com.djamware.springangularauth.configs;
 
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

Next, add these lines of codes that contain component annotation, the class name that extends the SecurityConfigurerAdapter, variables, and methods.

public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
 
    private JwtTokenProvider jwtTokenProvider;
 
    public JwtConfigurer(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }
 
    @Override
    public void configure(HttpSecurity http) throws Exception {
        JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider);
        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

Finally, we have to configure the Spring Security by open and edit src/main/java/com/djamware/springangularauth/configs/WebSecurityConfig.java then add these package name and imports.

package com.djamware.springangularauth.configs;
 
import javax.servlet.http.HttpServletResponse;
 
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.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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
 
import com.djamware.springangularauth.services.CustomUserDetailsService;

Add these lines of codes that contains Configuration and EnableWebSecurity annotation, class name that extends WebSecurityConfigurerAdapter, required variables, constructors, and methods.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    JwtTokenProvider jwtTokenProvider;
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        UserDetailsService userDetailsService = mongoUserDetails();
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
 
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic().disable().csrf().disable().sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
                .antMatchers("/api/auth/login").permitAll().antMatchers("/api/auth/register").permitAll()
                .antMatchers("/api/products/**").hasAuthority("ADMIN").anyRequest().authenticated().and().csrf()
                .disable().exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint()).and()
                .apply(new JwtConfigurer(jwtTokenProvider));
    http.cors();
    }
 
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**");
    }
 
    @Bean
    public PasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
 
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
 
    @Bean
    public AuthenticationEntryPoint unauthorizedEntryPoint() {
        return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
                "Unauthorized");
    }
 
    @Bean
    public UserDetailsService mongoUserDetails() {
        return new CustomUserDetailsService();
    }
}

Create Product and Authentication Controllers

Now it's time for REST API endpoint. All RESTful API will be created from each controller. Product controller will handle CRUD endpoint of product and Authentication controller will handle login and register endpoint. Create a new folder and files for the controllers.

mkdir src/main/java/com/djamware/springangularauth/controllers
touch src/main/java/com/djamware/springangularauth/controllers/ProductController.java
touch src/main/java/com/djamware/springangularauth/controllers/AuthController.java
touch src/main/java/com/djamware/springangularauth/controllers/AuthBody.java

Next, open and edit src/main/java/com/djamware/springangularauth/controllers/AuthController.java then add these package name and imports.

package com.djamware.springangularauth.controllers;
 
import java.util.Optional;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import com.djamware.springangularauth.models.Products;
import com.djamware.springangularauth.repositories.ProductRepository;

Add these lines of codes that contain RestController annotation, class name, required variables, constructors, and methods.

@CrossOrigin(origins = "*")
@RequestMapping("/api")
@RestController
public class ProductController {
 
    @Autowired
    ProductRepository productRepository;
 
    @RequestMapping(method = RequestMethod.GET, value = "/products")
    public Iterable<Products> product() {
        return productRepository.findAll();
    }
}

Next, open and edit src/main/java/com/djamware/springangularauth/controllers/AuthController.java then add these package name and imports.

package com.djamware.springangularauth.controllers;
 
import static org.springframework.http.ResponseEntity.ok;
 
import java.util.HashMap;
import java.util.Map;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import com.djamware.springangularauth.configs.JwtTokenProvider;
import com.djamware.springangularauth.models.User;
import com.djamware.springangularauth.repositories.UserRepository;
import com.djamware.springangularauth.services.CustomUserDetailsService;

Add these lines of codes that contain RestController and RequestMapping annotation, class name, required variables, constructors, and methods.

@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api/auth")
public class AuthController {
 
    @Autowired
    AuthenticationManager authenticationManager;
 
    @Autowired
    JwtTokenProvider jwtTokenProvider;
 
    @Autowired
    UserRepository users;
 
    @Autowired
    private CustomUserDetailsService userService;
 
    @SuppressWarnings("rawtypes")
    @PostMapping("/login")
    public ResponseEntity login(@RequestBody AuthBody data) {
        try {
            String username = data.getEmail();
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, data.getPassword()));
            String token = jwtTokenProvider.createToken(username, this.users.findByEmail(username).getRoles());
            Map<Object, Object> model = new HashMap<>();
            model.put("username", username);
            model.put("token", token);
            return ok(model);
        } catch (AuthenticationException e) {
            throw new BadCredentialsException("Invalid email/password supplied");
        }
    }
 
    @SuppressWarnings("rawtypes")
    @PostMapping("/register")
    public ResponseEntity register(@RequestBody User user) {
        User userExists = userService.findUserByEmail(user.getEmail());
        if (userExists != null) {
            throw new BadCredentialsException("User with username: " + user.getEmail() + " already exists");
        }
        userService.saveUser(user);
        Map<Object, Object> model = new HashMap<>();
        model.put("message", "User registered successfully");
        return ok(model);
    }
}

Next, open and edit src/main/java/com/djamware/springangularauth/controllers/AuthBody.java then add these package name.

package com.djamware.springangularauth.controllers;

Add these lines of codes that contains the class name, fields or variables, and getter/setter for those variables.

public class AuthBody {
 
    private String email;
    private String password;
 
    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;
    }
 
}

Install or Update Angular 8 CLI and Create Application

Before installing the Angular 8 CLI, make sure you have installed Node.js https://nodejs.org and can open Node.js command prompt. Next, open the Node.js command prompt then type this command to install

Angular 8 CLI.

sudo npm install -g @angular/cli

Next, create an Angular 8 application by typing this command in the root of the Spring Boot application/project directory.

ng new client

Where client is the name of the Angular 8 application. You can specify your own name, we like to name it client because it's put inside Spring Boot Project directory. If there's a question, we fill them with Y and SCSS. Next, go to the newly created Angular 8 application.

cd client

Run the Angular 8 application for the first time.

ng serve

Now, go to localhost:4200 and you should see this page.

Add Routes for Navigation between Angular 8 Pages/Component

On the previous steps, we have to add Angular 8 Routes when answering the questions. Now, we just added the required pages for CRUD (Create, Read, Update, Delete) Supplier data. Type this commands to add the Angular 8 components or pages.

ng g component products
ng g component auth/login
ng g component auth/register

Open src/app/app.module.ts then you will see those components imported and declared in @NgModule declarations. Next, open and edit src/app/app-routing.module.ts then add these imports.

import { ProductsComponent } from './products/products.component';
import { LoginComponent } from './auth/login/login.component';
import { RegisterComponent } from './auth/register/register.component';

Add these arrays to the existing routes constant.

const routes: Routes = [
  {
    path: '',
    redirectTo: 'products',
    pathMatch: 'full'
  },
  {
    path: 'products',
    component: ProductsComponent,
    data: { title: 'List of Products' }
  },
  {
    path: 'login',
    component: LoginComponent,
    data: { title: 'Login' }
  },
  {
    path: 'register',
    component: RegisterComponent,
    data: { title: 'Register' }
  }
];

Open and edit src/app/app.component.html and you will see the existing router outlet. Next, modify this HTML page to fit the CRUD page.

<div class="container">
  <router-outlet></router-outlet>
</div>

Open and edit src/app/app.component.scss then replace all SCSS codes with this.

.container {
  padding: 20px;
}

Create a custom Angular 8 HttpInterceptor

Before creating a custom Angular 8 HttpInterceptor, create a folder with the name client/src/app/interceptors. Next, create a file for the custom Angular 8 HttpInterceptor with the name client/src/app/interceptors/token.interceptor.ts. Open and edit that file the add these imports.

import { Injectable } from '@angular/core';
import {
    HttpRequest,
    HttpHandler,
    HttpEvent,
    HttpInterceptor,
    HttpResponse,
    HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { Router } from '@angular/router';

Create a class that implementing HttpInterceptor method.

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
 
}

Inject the required module to the constructor inside the class.

constructor(private router: Router) {}

Implement a custom Interceptor function.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 
    const token = localStorage.getItem('token');
    if (token) {
      request = request.clone({
        setHeaders: {
          'Authorization': 'Bearer ' + token
        }
      });
    }
    if (!request.headers.has('Content-Type')) {
      request = request.clone({
        setHeaders: {
          'content-type': 'application/json'
        }
      });
    }
    request = request.clone({
      headers: request.headers.set('Accept', 'application/json')
    });
    return next.handle(request).pipe(
      map((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          console.log('event--->>>', event);
        }
        return event;
      }),
      catchError((error: HttpErrorResponse) => {
        console.log(error);
        if (error.status === 401) {
          this.router.navigate(['login']);
        }
        if (error.status === 400) {
          alert(error.error);
        }
        return throwError(error);
      }));
}

Next, we have to register this custom HttpInterceptor and HttpClientModule. Open and edit client/src/app.module.ts then add these imports.

import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; 
import { TokenInterceptor } from './interceptors/token.interceptor';

Add HttpClientModule to the @NgModule imports array.

imports: [
  BrowserModule,
  AppRoutingModule,
  HttpClientModule
],

Add the Interceptor modules to the provider array of the @NgModule.

providers: [
  {
    provide: HTTP_INTERCEPTORS,
    useClass: TokenInterceptor,
    multi: true
  }
],

Now, the HTTP interceptor is ready to intercept any request to the API.

Create Services for Accessing Product and Authentication API

To access the Spring Boot RESTful API from Angular 8 application, we have to create services for that. Type these commands to generate the Angular 8 services from the client folder.

ng g service auth 
ng g service product

Next, open and edit client/src/app/auth.service.ts then add these imports.

import { HttpClient } from '@angular/common/http'; 
import { Observable, of } from 'rxjs'; 
import { catchError, tap } from 'rxjs/operators';

Declare a constant variable as Spring Boot RESTful API URL after the imports.

const apiUrl = 'http://192.168.0.7:8080/api/auth/';

Declare the variables before the constructor that will use by Angular 8 Route Guard.

isLoggedIn = false; 
redirectUrl: string;

Inject the HttpClient module inside the constructor.

constructor(private http: HttpClient) { }

Create all required functions for Login, Logout, Register, and helper functions.

login(data: any): Observable<any> {
  return this.http.post<any>(apiUrl + 'login', data)
    .pipe(
      tap(_ => this.isLoggedIn = true),
      catchError(this.handleError('login', []))
    );
}
 
logout(): Observable<any> {
  return this.http.get<any>(apiUrl + 'signout')
    .pipe(
      tap(_ => this.isLoggedIn = false),
      catchError(this.handleError('logout', []))
    );
}
 
register(data: any): Observable<any> {
  return this.http.post<any>(apiUrl + 'register', data)
    .pipe(
      tap(_ => this.log('login')),
      catchError(this.handleError('login', []))
    );
}
 
private handleError<T>(operation = 'operation', result?: T) {
  return (error: any): Observable<T> => {
 
    console.error(error); // log to console instead
    this.log(`${operation} failed: ${error.message}`);
 
    return of(result as T);
  };
}
 
private log(message: string) {
  console.log(message);
}

Next, create an object class that represents Product data client/src/app/products/product.ts then replace all file contents with these.

export class Product {
    productId: number;
    isbn: string;
    title: string;
    author: string;
    description: string;
    publisher: string;
    publishedYear: number;
    price: number;
}

Next, open and edit client/src/app/services/product.service.ts then replace all codes with this.

import { Injectable } from '@angular/core';
import { Product } from './products/product';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
 
const apiUrl = 'http://192.168.0.7:8080/api/products';
 
@Injectable({
  providedIn: 'root'
})
export class ProductService {
 
  constructor(private http: HttpClient) { }
 
  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(apiUrl + 'Product')
      .pipe(
        tap(_ => this.log('fetched Products')),
        catchError(this.handleError('getProducts', []))
      );
  }
 
  private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
 
      console.error(error); // log to console instead
      this.log(`${operation} failed: ${error.message}`);
 
      return of(result as T);
    };
  }
 
  private log(message: string) {
    console.log(message);
  }
}

Display List of Product using Angular 8 Material

To display a list of products to the Angular 8 template. First, open and edit client/src/app/products/products.component.ts then add these imports.

import { Product } from './product'; 
import { ProductService } from '../product.service'; 
import { AuthService } from '../auth.service'; 
import { Router } from '@angular/router';

Next, inject the Product and Auth Services to the constructor.

constructor(private productService: ProductService, private authService: AuthService, private router: Router) { }

Declare these variables before the constructor.

data: Product[] = [];
displayedColumns: string[] = ['prodName', 'prodDesc', 'prodPrice'];
isLoadingResults = true;

Create a function for consuming or get a product list from the producing service.

getProducts(): void {
  this.productService.getProducts()
    .subscribe(products => {
      this.data = products;
      console.log(this.data);
      this.isLoadingResults = false;
    }, err => {
      console.log(err);
      this.isLoadingResults = false;
    });
}

Call this function from ngOnInit.

ngOnInit() {
  this.getProducts();
}

Add a function for log out the current session.

logout() {
  localStorage.removeItem('token');
  this.router.navigate(['login']);
}

Next, for the user interface (UI) we will use Angular 8 Material and CDK. There's a CLI for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular 8 Material.

ng add @angular/material

If there are some questions, answer them like below.

? Choose a prebuilt theme name, or "custom" for a custom theme: Purple/Green       [ Preview: https://material.angular.i
o?theme=purple-green ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

Next, we have to register all required Angular Material components or modules to src/app/app.module.ts. Open and edit that file then add this imports.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from '@angular/material';

Also, add FormsModule and ReactiveFormsModule.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to @NgModule imports.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule,
  ReactiveFormsModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

Next, open and edit client/src/app/products/products.component.html then replace all HTML tags with this Angular 8 Material tags.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" (click)="logout()">Logout</a>
  </div>
  <div class="mat-elevation-z8">
    <table mat-table [dataSource]="data" class="example-table">
 
      <!-- Product ID Column -->
      <ng-container matColumnDef="prodName">
        <th mat-header-cell *matHeaderCellDef>Product Name</th>
        <td mat-cell *matCellDef="let row">{{row.prodName}}</td>
      </ng-container>
 
      <!-- ISBN Column -->
      <ng-container matColumnDef="prodDesc">
        <th mat-header-cell *matHeaderCellDef>Product Description</th>
        <td mat-cell *matCellDef="let row">{{row.prodDesc}}</td>
      </ng-container>
 
      <!-- Title Column -->
      <ng-container matColumnDef="prodPrice">
        <th mat-header-cell *matHeaderCellDef>Product Price</th>
        <td mat-cell *matCellDef="let row">{{row.prodPrice}}</td>
      </ng-container>
 
      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
    </table>
  </div>
</div>

Finally, we have to align the style for this page. Open and edit client/src/app/products/products.component.scss then replace all SCSS codes with these.

/* Structure */
.example-container {
    position: relative;
    padding: 5px;
}
 
.example-table-container {
    position: relative;
    max-height: 400px;
    overflow: auto;
}
 
table {
    width: 100%;
}
 
.example-loading-shade {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 56px;
    right: 0;
    background: rgba(0, 0, 0, 0.15);
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}
 
.example-rate-limit-reached {
    color: #980000;
    max-width: 360px;
    text-align: center;
}
 
/* Column Widths */
.mat-column-number,
.mat-column-state {
    max-width: 64px;
}
 
.mat-column-created {
    max-width: 124px;
}
 
.mat-flat-button {
    margin: 5px;
}

Create the Angular 8 Login and Register Page

This time for authentication part. Open and edit client/src/app/auth/login/login.component.ts then add these imports.

import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { AuthService } from '../../auth.service';
import { Router } from '@angular/router';
import { ErrorStateMatcher } from '@angular/material/core';

Declare these variables before the constructor.

loginForm: FormGroup;
email = '';
password = '';
matcher = new MyErrorStateMatcher();
isLoadingResults = false;

Inject the imported modules to the constructor.

constructor(private formBuilder: FormBuilder, private router: Router, private authService: AuthService) { }

Initialize NgForm to the NgOnInit function.

ngOnInit() {
  this.loginForm = this.formBuilder.group({
    'email' : [null, Validators.required],
    'password' : [null, Validators.required]
  });
}

Add a function to submit the login form.

onFormSubmit(form: NgForm) {
  this.authService.login(form)
    .subscribe(res => {
      console.log(res);
      if (res.token) {
        localStorage.setItem('token', res.token);
        this.router.navigate(['products']);
      }
    }, (err) => {
      console.log(err);
    });
}

Add a function to go to the Register page.

register() {
  this.router.navigate(['register']);
}

Add a class that handles the form validation above this class.

/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Next, open and edit client/src/app/auth/login/login.component.html then replace all HTML tags with these.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <mat-card class="example-card">
    <form [formGroup]="loginForm" (ngSubmit)="onFormSubmit(loginForm.value)">
      <mat-form-field class="example-full-width">
        <input matInput type="email" placeholder="Email" formControlName="email"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!loginForm.get('email').valid && loginForm.get('email').touched">Please enter your email</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput type="password" placeholder="Password" formControlName="password"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!loginForm.get('password').valid && loginForm.get('password').touched">Please enter your password</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
        <button type="submit" [disabled]="!loginForm.valid" mat-flat-button color="primary">Login</button>
      </div>
      <div class="button-row">
        <button type="button" mat-flat-button color="primary" (click)="register()">Register</button>
      </div>
    </form>
  </mat-card>
</div>

Next, give this page a style by open and edit client/src/app/auth/login/login.component.scss then applies these styles codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}
 
.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}
 
.example-full-width {
  width: 100%;
}
 
.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}
 
.button-row {
  margin: 10px 0;
}
 
.mat-flat-button {
  margin: 5px;
}

Next, for register page, open and edit client/src/app/auth/register/register.component.ts then replace all Typescript codes with these.

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { AuthService } from '../../auth.service';
import { Router } from '@angular/router';
import { ErrorStateMatcher } from '@angular/material/core';
 
@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.scss']
})
export class RegisterComponent implements OnInit {
 
  registerForm: FormGroup;
  fullName = '';
  email = '';
  password = '';
  isLoadingResults = false;
  matcher = new MyErrorStateMatcher();
 
  constructor(private formBuilder: FormBuilder, private router: Router, private authService: AuthService) { }
 
  ngOnInit() {
    this.registerForm = this.formBuilder.group({
      'fullName' : [null, Validators.required],
      'email' : [null, Validators.required],
      'password' : [null, Validators.required]
    });
  }
 
  onFormSubmit(form: NgForm) {
    this.authService.register(form)
      .subscribe(res => {
        this.router.navigate(['login']);
      }, (err) => {
        console.log(err);
        alert(err.error);
      });
  }
 
}
 
/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

Next, open and edit client/src/app/auth/register/register.component.html then replace all HTML tags with these.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <mat-card class="example-card">
    <form [formGroup]="registerForm" (ngSubmit)="onFormSubmit(registerForm.value)">
      <mat-form-field class="example-full-width">
        <input matInput type="fullName" placeholder="Full Name" formControlName="fullName"
                [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!registerForm.get('fullName').valid && registerForm.get('fullName').touched">Please enter your Full Name</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput type="email" placeholder="Email" formControlName="email"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!registerForm.get('email').valid && registerForm.get('email').touched">Please enter your email</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput type="password" placeholder="Password" formControlName="password"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!registerForm.get('password').valid && registerForm.get('password').touched">Please enter your password</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
        <button type="submit" [disabled]="!registerForm.valid" mat-flat-button color="primary">Register</button>
      </div>
    </form>
  </mat-card>
</div>

Finally, open and edit client/src/app/auth/register/register.component.scss then replace all SCSS codes with these.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}
 
.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}
 
.example-full-width {
  width: 100%;
}
 
.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}
 
.button-row {
  margin: 10px 0;
}
 
.mat-flat-button {
  margin: 5px;
}

Secure the Guarded Products Page using Angular 8 Route Guard

As we mention in the beginning that the Angular 8 application will use Angular 8 Route Guard to secure the products page. So, we have both securities for the Angular 8 page and for Spring Boot RESTful API. Type this command to generate a guard configuration file.

ng generate guard auth/auth

Open and edit that file then add this Angular 8 or Typescript imports.

import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../auth.service';

Next, add this implements code to the Class name.

export class AuthGuard implements CanActivate

Inject the AuthService and the Router to the constructor params.

constructor(private authService: AuthService, private router: Router) {}

Add the function for the Route Guard.

canActivate(
  next: ActivatedRouteSnapshot,
  state: RouterStateSnapshot): boolean {
  const url: string = state.url;
 
  return this.checkLogin(url);
}

Add the function to check the login status and redirect to the login page if it's not logged in and redirect to the Guarded page if it's logged in.

checkLogin(url: string): boolean {
  if (this.authService.isLoggedIn) { return true; }
 
  // Store the attempted URL for redirecting
  this.authService.redirectUrl = url;
 
  // Navigate to the login page with extras
  this.router.navigate(['/login']);
  return false;
}

Next, open and edit src/app/app-routing.module.ts then add this import.

import { AuthGuard } from './auth/auth.guard';

Modify the product path, so it will look like this.

const routes: Routes = [
  {
    path: 'products',
    canActivate: [AuthGuard],
    component: ProductsComponent,
    data: { title: 'List of Products' }
  },
  {
    path: 'login',
    component: LoginComponent,
    data: { title: 'Login' }
  },
  {
    path: 'register',
    component: RegisterComponent,
    data: { title: 'Register' }
  }
];

Run and Test The Authentication of The Spring Boot, Security, MongoDB, and Angular 8 Web Application

Before run the Spring Boot RESTful API, make sure the MongoDB server is running by type this command in another terminal or command line tab.

mongod

In the different tab run the Spring Boot RESTful API using this command.

gradle bootRun

Open again a new terminal tab then go to the client folder then run the Angular 8 application.

ng serve

Next, open the browser then go to [http://localhost:4200](http://localhost:4200 "http://localhost:4200") and you

should see then login page because of the landing page that point to

products page will redirect to login if not logged in.

That it's the Spring Boot, Security, MongoDB, Angular 8: Build Authentication. You can find the full source code from our GitHub.