How to create a simple web application with Java 8, Spring Boot and Angular

How to create a simple web application with Java 8, Spring Boot and Angular

In this tutorial, we'll look at how developers can combine multiple technologies to make a web application. Read on to get started!

Pre-Requisites for Getting Started
  • Java 8 is installed.
  • Any Java IDE (preferably STS or IntelliJ IDEA).
  • Basic understanding of Java and Spring-based web development and UI development using HTML, CSS, and JavaScript.
Backdrop

In this article, I will try to create a small end-to-end web application using Java 8 and Spring Boot.

I have chosen SpringBoot because it is much easier to configure and plays well with other tech stacks. I have also used a REST API and SpringData JPA with an H2 database.

I used Spring Initializer to add all the dependencies and create a blank working project with all my configurations.

I have used Maven as the build tool, though Gradle can also be used.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>bootdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>bootDemo</name>
    <description>Demo project for Spring Boot</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath />
        <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.restdocs</groupId>
            <artifactId>spring-restdocs-mockmvc</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

In the UI part, I have used AngularJS and BootStrap CSS along with basic JS, CSS, and HTML.

I tried to follow the coding guidelines as much as I could, but all suggestions are welcome.

This is a very simple project which can be useful for creating an end-to-end web application.

Package Structure

Implementation

Let's start with the SpringBootApplication class.

@SpringBootApplication
public class BootDemoApplication {
 @Autowired
 UserRepository userRepository;

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

Let's create Controllers now. 

@Controller
public class HomeController {
 @RequestMapping("/home")
 public String home() {
  return "index";
 }
}

This will act as the homepage for our SPA. Now we create a Controller to handle some REST calls.

@RequestMapping("/user")
@RestController
public class UserController {
 @Autowired
 UserService userService;

 @RequestMapping(Constants.GET_USER_BY_ID)
 public UserDto getUserById(@PathVariable Integer userId) {
  return userService.getUserById(userId);
 }

 @RequestMapping(Constants.GET_ALL_USERS)
 public List < UserDto > getAllUsers() {
  return userService.getAllUsers();
 }

 @RequestMapping(value = Constants.SAVE_USER, method = RequestMethod.POST)
 public void saveUser(@RequestBody UserDto userDto) {
  userService.saveUser(userDto);
 }
}

Here we have different methods to handle different test calls from the client side.

I have Autowired a Service class, UserService, in the Controller.

public interface UserService {
 UserDto getUserById(Integer userId);
 void saveUser(UserDto userDto);
 List < UserDto > getAllUsers();
}

@Service
public class UserServiceimpl implements UserService {
 @Autowired
 UserRepository userRepository;

 @Override
 public UserDto getUserById(Integer userId) {
  return UserConverter.entityToDto(userRepository.getOne(userId));
 }

 @Override
 public void saveUser(UserDto userDto) {
  userRepository.save(UserConverter.dtoToEntity(userDto));
 }

 @Override
 public List < UserDto > getAllUsers() {
  return userRepository.findAll().stream().map(UserConverter::entityToDto).collect(Collectors.toList());
 }
}

In a typical web application, there are generally two types of data objects: DTO (to communicate through the client) and Entity (to communicate through the DB).

DTO

public class UserDto {
    Integer userId;
    String userName;
    List<SkillDto> skillDtos= new ArrayList<>();
    public UserDto(Integer userId, String userName, List<SkillDto> skillDtos) {
        this.userId = userId;
        this.userName = userName;
        this.skillDtos = skillDtos;
    }
    public UserDto() {
    }
    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public List<SkillDto> getSkillDtos() {
        return skillDtos;
    }
    public void setSkillDtos(List<SkillDto> skillDtos) {
        this.skillDtos = skillDtos;
    }
}
public class SkillDto {
    Integer skillId;
    String SkillName;
    public SkillDto(Integer skillId, String skillName) {
        this.skillId = skillId;
        SkillName = skillName;
    }
    public SkillDto() {
    }
    public Integer getSkillId() {
        return skillId;
    }
    public void setSkillId(Integer skillId) {
        this.skillId = skillId;
    }
    public String getSkillName() {
        return SkillName;
    }
    public void setSkillName(String skillName) {
        SkillName = skillName;
    }
}

Entity

@Entity
public class User implements Serializable{
    private static final long serialVersionUID = 0x62A6DA99AABDA8A8L;
@Column
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private Integer userId;
    @Column
    private String userName;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<Skill> skills= new LinkedList<>();
    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public List<Skill> getSkills() {
        return skills;
    }
    public void setSkills(List<Skill> skills) {
        this.skills = skills;
    }
    public User() {
    }
    public User(String userName, List<Skill> skills) {
        this.userName = userName;
        this.skills = skills;
    }
}
@Entity
public class Skill {
    @Column
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private Integer skillId;
    @Column
    private String skillName;
    @ManyToOne
    private User user;
    public Skill(String skillName) {
this.skillName = skillName;
}
public Integer getSkillId() {
        return skillId;
    }
    public void setSkillId(Integer skillId) {
        this.skillId = skillId;
    }
    public String getSkillName() {
        return skillName;
    }
    public void setSkillName(String skillName) {
        this.skillName = skillName;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    public Skill() {
    }
    public Skill(String skillName, User user) {
        this.skillName = skillName;
        this.user = user;
    }
}

For DB operations, we use SpringData JPA:

@Repository
public interface UserRepository extends JpaRepository<User, Integer>{
}

@Repository
public interface SkillRepository extends JpaRepository<Skill, Integer>{
}

Extending JpaRepository provides a lot of CRUD operations by default, and one can use it to create their own query methods as well. Please read more about this here.

To convert DTO -> Entity and Entity -> DTO, I created some basic converter classes.

public class UserConverter {
 public static User dtoToEntity(UserDto userDto) {
  User user = new User(userDto.getUserName(), null);
  user.setUserId(userDto.getUserId());
  user.setSkills(userDto.getSkillDtos().stream().map(SkillConverter::dtoToEntity).collect(Collectors.toList()));
  return user;
 }

 public static UserDto entityToDto(User user) {
  UserDto userDto = new UserDto(user.getUserId(), user.getUserName(), null);
  userDto.setSkillDtos(user.getSkills().stream().map(SkillConverter::entityToDto).collect(Collectors.toList()));
  return userDto;
 }
}

public class SkillConverter {
 public static Skill dtoToEntity(SkillDto SkillDto) {
  Skill Skill = new Skill(SkillDto.getSkillName(), null);
  Skill.setSkillId(SkillDto.getSkillId());
  return Skill;
 }

 public static SkillDto entityToDto(Skill skill) {
  return new SkillDto(skill.getSkillId(), skill.getSkillName());
 }
}

Let's focus on the UI part now.

While using Angular, there are certain guidelines we need to follow.

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="ISO-8859-1">
    <title>Main Page</title>
</head>
<body ng-app="demo">
<hr/>
<div class="container" ng-controller="UserController">
    <div class="row">
        <label>User</label> <input type="text" ng-model="userDto.userName" class="input-sm spacing"/>
        <label>Skills</label> <input type="text" ng-model="skills" ng-list class="input-sm custom-width spacing"
                                     placeholder="use comma to separate skills"/>
        <button ng-click="saveUser()" class="btn btn-sm btn-info">Save User</button>
    </div>
    <hr/>
    <div class="row">
        <p>{{allUsers | json}}</p>
    </div>
    <hr/>
    <div class="row" ng-repeat="user in allUsers">
        <div class="">
            <h3>{{user.userName}}</h3>
            <span ng-repeat="skill in user.skillDtos" class="spacing">{{skill.skillName}}</span>
        </div>
    </div>
</div>
</body>
<script src="js/lib/angular.min.js"></script>
<script src="js/lib/ui-bootstrap-tpls-2.5.0.min.js"></script>
<script src="js/app/app.js"></script>
<script src="js/app/UserController.js"></script>
<script src="js/app/UserService.js"></script>
<link rel="stylesheet" href="css/lib/bootstrap.min.css"/>
<link rel="stylesheet" href="css/app/app.css"/>
</html>

While creating the HTML, don't forget to import the required JS and CSS files.

app.js

'use strict'

var demoApp = angular.module('demo', ['ui.bootstrap', 'demo.controllers',
    'demo.services'
]);
demoApp.constant("CONSTANTS", {
    getUserByIdUrl: "/user/getUser/",
    getAllUsers: "/user/getAllUsers",
    saveUser: "/user/saveUser"
});

UserController.js

'use strict'

var module = angular.module('demo.controllers', []);
module.controller("UserController", ["$scope", "UserService",
    function($scope, UserService) {

        $scope.userDto = {
            userId: null,
            userName: null,
            skillDtos: []
        };
        $scope.skills = [];

        UserService.getUserById(1).then(function(value) {
            console.log(value.data);
        }, function(reason) {
            console.log("error occured");
        }, function(value) {
            console.log("no callback");
        });

        $scope.saveUser = function() {
            $scope.userDto.skillDtos = $scope.skills.map(skill => {
                return {
                    skillId: null,
                    skillName: skill
                };
            });
            UserService.saveUser($scope.userDto).then(function() {
                console.log("works");
                UserService.getAllUsers().then(function(value) {
                    $scope.allUsers = value.data;
                }, function(reason) {
                    console.log("error occured");
                }, function(value) {
                    console.log("no callback");
                });
 
               $scope.skills = [];
                $scope.userDto = {
                    userId: null,
                    userName: null,
                    skillDtos: []
                };
            }, function(reason) {
                console.log("error occured");
            }, function(value) {
                console.log("no callback");
            });
        }
    }
]);


UserService.js

use strict'

angular.module('demo.services', []).factory('UserService', ["$http", "CONSTANTS", function($http, CONSTANTS) {
    var service = {};
    service.getUserById = function(userId) {
        var url = CONSTANTS.getUserByIdUrl + userId;
        return $http.get(url);
    }
    service.getAllUsers = function() {
        return $http.get(CONSTANTS.getAllUsers);
    }
    service.saveUser = function(userDto) {
        return $http.post(CONSTANTS.saveUser, userDto);
    }
    return service;
}]);

app.css

body{
    background-color: #efefef;
}
span.spacing{
    margin-right: 10px;
}
input.custom-width{
    width: 200px;
}
input.spacing{
    margin-right: 5px;
}

The application can be built using:

mvn&nbsp;clean install then run as a runnable jar file by using

java -jar bootdemo-0.0.1-SNAPSHOT.jar or running the main file directly.

Open the browser and hit http://localhost:8080/home

One simple page will open. Enter the name and skills and the entered data will be persisted in the DB.

In the application.properties files, two configurations are to be added.

spring.mvc.view.prefix = /views/
spring.mvc.view.suffix = .html

The source code can be cloned or downloaded from here.

Originally published by Ashish Lohia  at dzone.com

================================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Go Full Stack with Spring Boot and Angular

Angular7 CRUD with nodejs and mysql example

Full Stack Web Development with Angular and Spring MVC

Building a Web Application Using Spring Boot, Angular, and Maven

Angular 7 + Spring Boot CRUD Example

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Building a Web Application Using Spring Boot, Angular, and Maven

Building a Web Application Using Spring Boot, Angular, and Maven

In this article, we will explore the steps used to build a web application using Spring Boot with Angular and Maven and then launch it on a Tomcat Server.

In this article, we will explore the steps used to build a web application using Spring Boot with Angular and Maven and then launch it on a Tomcat Server.

Angular and Spring Boot are both great frameworks which are nowadays in great combination especially by java developers gladly used for building microservices.

In this article I want to show therefore how you can setup a parent maven project which includes an angular and spring boot child, which is finally be deployed on a tomcat server, including production ready jar with some pre-requisties.

Pre-Requisites

Since we are building the project locally and deploy, it’s recommended to try the below mentioned versions and then upgrade or downgrade as per your requirements.

Most importantly, you should also ahve some knowlegde of Spring, Angular, and Maven.

  • Maven version 3.5.2
  • Node version v8.9.3
  • Npm version v5.5.1
  • IntelliJ or any other IDE of your favourite
What Are We Going to Do?

Let’s start building the Spring Boot Project with the following structure.

I have named it AppName — you can specify the any application name youe like.

To begin, it’s recommneded to use https://start.spring.io because its simple and ease to use.

Also, you will need minimum dependencies shown above under the ‘Selected dependencies’ header.

Create Modules for Angular and a Service Module

Once you have downloaded a project and imported it into your favorite IDE, (mine is IntelliJ), right-click on the parent module, i.e AppName, and click ‘New Module’ as shown below.

It’s recommended that you create a module with the project name suffixed with the module name. For example, AppName-UI or AppName-Service.

Once the modules are created, you can see that each module will havea pom.xml file associated with it, as shown in the figure below.

Now its time to discuess what are the modules used for and their purpose.

AppName-Service

This module is used for generating the final depolyment jar which, in turn, has core logic, REST controllers, models, repository classes, etc.

It would also have all the HTML, CSS, and JS files needed for the application.

AppName-UI

This module will contain the Angular project along with the pom.xml

Let’s now examine the pom.xml file used in the project.

The struture of parent pom.xml in the AppName folder of the pom.xml in AppName-Service module are as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>AppName</artifactId>
        <groupId>com.appname.tutorial</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>

    <artifactId>AppName-Service</artifactId>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Note: You have to remove the build configuration from the Parent pom.xml and place it the AppName-Service’s pom.xml.

Also, it’s important to add packaging in the AppName-Service’s pom.xml.

<packaging>Jar</packaging>

Let’s now look at building the Angular project.

You have to delete the src folder created in the AppName-UI module/folder.

Copy the pom.xml from the AppName-UI module and place it in your Desktop.

Navigate to AppName from the terminal and run the following commands:

cd ~/AppName
rm -rf AppName-UI

Create an Angular 6 Project Using Angular-CLI

Know more about https://cli.angular.io/.

Generate an Angular 6 project using the Angular-CLI from the AppName directory/folder. Navigate to the AppName directoryin the terminal and run the following command:

ng new AppName-UI

Once the above command has been run, you will see that Angular CLI generates a folder/directory including node_modules, package.json, angular-cli.json, etc.

Just to ensure the newly created Angular app runs fine, you can navigate to that folder and run the below command

ng serve

Note:Replace the pom.xml file from the Desktop which we copied in the previous step.

The structure of pom.xml file in the AppName-UI is as follows.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>AppName</artifactId>
        <groupId>com.appname.tutorial</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>

    <artifactId>AppName-UI</artifactId>

    <build>
        <plugins>
            <plugin>
                <groupId>com.github.eirslett</groupId>
                <artifactId>frontend-maven-plugin</artifactId>
                <version>0.0.28</version>
                <configuration>
                    <workingDirectory>./</workingDirectory>
                    <nodeVersion>${node.version}</nodeVersion>
                    <npmVersion>${npm.version}</npmVersion>
                    <nodeDownloadRoot>http://nodejs.org/dist/</nodeDownloadRoot>
                    <npmDownloadRoot>http://registry.npmjs.org/npm/-/</npmDownloadRoot>
                    <installDirectory>./</installDirectory>
                </configuration>
                <executions>
                    <execution>
                        <id>install node and npm</id>
                        <goals>
                            <goal>install-node-and-npm</goal>
                        </goals>
                        <phase>generate-resources</phase>
                    </execution>
                    <execution>
                        <id>npm install</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                        <phase>generate-resources</phase>
                        <configuration>
                            <arguments>ci --loglevel=error</arguments>
                        </configuration>
                    </execution>
                    <execution>
                        <id>npm run-script build-prod</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                        <configuration>
                            <arguments>run-script build-dev</arguments>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>

    </build>
</project>


Where Are We Now?

We have created a Spring Boot application running on port 8080. We’ve also created an Angular application running on port 4200.

But we still haven’t achieve our goal of running an Angular application on Spring Boot’s Tomcat Server.

Running an Angular Application on Spring Boot’s Tomcat Server

As you already know, when use ng serve or ng build, Angular CLI generates all the HTML, CSS, andJS files in the dist folder.

Now we need to move that dist folder to AppName-Service’s resources directory.

The easiest way to do that is to open the angular-cli.json file of our Angular application and modify the outDir property as shown below

So, basically, what we are saying is to put all the generated files into the resources of service folder.

Run the Application

Now it’s all yours.

Run mvn clean installfrom the project root directory. This will generate a jar file in the AppName-Service/target directory. It can be deployed to the Tomcat Server and the application can be viewed.

To run the Spring Boot application using Maven, run the following command from the AppName-Servicedirectory.

mvn spring-boot:run

Once the application has started, we should be able to see the index.html in the logs, and open the browser navigate to http://localhost:8080/index.html to see the welcome page.

Angular HTML page accessed via a Tomcat Server.

Complete source code is available on GitHub: https://github.com/ihappyk/AppName

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

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.