Spring Boot + Spring Security + Angular Example Tutorial


In the previous tutorial, we have implemented an Angular 8 + Spring boot hello world example. In this tutorial, we will be implementing Basic login authentication using Spring security to secure REST service that created in the previous tutorial. We will implement basic login and logout features. All the REST calls made from Angular to Spring Boot will be authenticated using Basic Authentication(spring security).

Spring boot made the easiest way to secure REST services by adding a very simple dependency - spring boot starter security. It is the spring boot starter for implementing security in web applications as well as RESTful services.
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
</dependency>
In the previous tutorial, we have created a simple spring boot hello world application and we will continue with this application and implement login, logout features.
Let's first implement security in Spring boot hello world application. You can download the source code of this tutorial on my GitHub for your reference.

Get source code of this tutorial on my GitHub Repository.

Server Side - Security implementation in Spring boot application

Add spring-boot-starter-security Dependency

Spring boot made the easiest way to secure REST services by adding a very simple dependency - spring boot starter security. Let's add the spring-boot-starter-security dependency to our pom.xml file:
<?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>net.javaguides.springboot</groupId>
    <artifactId>Springboot-helloworld-application</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>Springboot-helloworld-application</name>
    <description>Demo project for Spring Boot</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.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-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Once Spring Security is on the classpath, then Spring Boot automatically secures all HTTP endpoints with "basic" authentication.

Default Security Setup

In order to add security to our Spring Boot application, we need to add the security starter dependency:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
This will include the SecurityAutoConfiguration class – containing the initial/default security configuration.
Simply put, by default, the Authentication gets enabled for the Application. Also, content negotiation is used to determine if basic or formLogin should be used.
There are some predefined properties, such as:
spring.security.user.name
spring.security.user.password
If we don’t configure the password using the predefined property spring.security.user.password and start the application, we’ll notice that a default password is randomly generated and printed in the console log:
Using the default security password:
 c8be15de-4488-4490-9dc6-fab3f91435c6

Configure standard user and password

We can override the default user and password using below properties in the application.properties file:
spring.security.user.name=javaguides
spring.security.user.password=password

Configure WebSecurityConfigurerAdapter

To enable authentication and authorization support in spring boot rest APIs, we can configure a utility class WebSecurityConfigurerAdapter. It helps in requiring the user to be authenticated prior to accessing any configured URL (or all URLs) within our application.
package net.javaguides.springboot.helloworldapp.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().
        disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .httpBasic();
    }
}

Define AuthenticationBean and BasicAuthController

Let's create an AuthenticationBean, which is used to return a success message to the client:
package net.javaguides.springboot.helloworldapp.bean;

public class AuthenticationBean {

    private String message;

    public AuthenticationBean(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return String.format("HelloWorldBean [message=%s]", message);
    }
}
Let's create BasicAuthController class with /basicauth REST API for returning the authentication success message.
package net.javaguides.springboot.helloworldapp.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import net.javaguides.springboot.helloworldapp.bean.AuthenticationBean;

@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("/api/v1")
public class BasicAuthController {

    @GetMapping(path = "/basicauth")
    public AuthenticationBean basicauth() {
        return new AuthenticationBean("You are authenticated");
    }
}

Testing above Security Implementation using Postman Rest Client

Frontend Side - Basic Login and Logout implementation in Angular 8 Application

We will be modifying the code we developed in the previous tutorial The angular project we will be developing is as follows - 

Login Authentication Implementation

Let's create a login component with the following command:
C:\angular-frontend\src\app> ng g c login
CREATE src/app/login/login.component.html (20 bytes)
CREATE src/app/login/login.component.spec.ts (621 bytes)
CREATE src/app/login/login.component.ts (265 bytes)
CREATE src/app/login/login.component.css (0 bytes)
UPDATE src/app/app.module.ts (724 bytes)
Let's create an Angular auth service with the following command:
PS C:\angular-frontend\src\app> ng g s /login/auth
CREATE src/app/login/auth.service.spec.ts (323 bytes)
CREATE src/app/login/auth.service.ts (133 bytes)
Let's understand each file generated above and implement the login feature.

LoginComponent

Open LoginComponent file and add the following code to it -
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { AuthenticationService } from './auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  username: string;
  password : string;
  errorMessage = 'Invalid Credentials';
  successMessage: string;
  invalidLogin = false;
  loginSuccess = false;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private authenticationService: AuthenticationService) {   }

  ngOnInit() {
  }

  handleLogin() {
    this.authenticationService.authenticationService(this.username, this.password).subscribe((result)=> {
      this.invalidLogin = false;
      this.loginSuccess = true;
      this.successMessage = 'Login Successful.';
      this.router.navigate(['/hello-world']);
    }, () => {
      this.invalidLogin = true;
      this.loginSuccess = false;
    });      
  }
}

Login HTML Template

Let's open login.component.html file and add the following code to it -
<app-menu></app-menu>
<div class="container col-lg-6">
    <h1 class="text-center">Login</h1>
    <div class="card">
      <div class="card-body">
        <form class="form-group">
          <div class="alert alert-warning" *ngIf='invalidLogin'>{{errorMessage}}</div>
          <div class="alert alert-success" *ngIf='loginSuccess'>{{successMessage}}</div>
          <div class="form-group">
            <label for="email">User Name :</label>
            <input type="text" class="form-control" id="username" [(ngModel)]="username" placeholder="Enter User Name"
              name="username">
          </div>
          <div class="form-group">
            <label for="pwd">Password:</label>
            <input type="password" class="form-control" [(ngModel)]="password" id="password" placeholder="Enter password"
              name="password">
          </div>
          <button (click)=handleLogin() class="btn btn-success">Login</button>
        </form>
      </div>
    </div>
  </div>

Add the login path to the routing module:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HelloWorldComponent } from './hello-world/hello-world.component';
import { LoginComponent } from './login/login.component';

const routes: Routes = [
  {path: 'login', component: LoginComponent},
  {path: '', component: LoginComponent},
  {path: 'hello-world', component: HelloWorldComponent},
  {path: 'logout', component: LoginComponent},
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }

AuthenticationService

Let's create AuthenticationService class which makes a basic auth Rest API call for basic authentication. We also need to store authenticated user in sessionStorage for logout implementation. 
Let's open auth.service.ts file and add the following content to it -
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {

  // BASE_PATH: 'http://localhost:8080'
  USER_NAME_SESSION_ATTRIBUTE_NAME = 'authenticatedUser'

  public username: String;
  public password: String;

  constructor(private http: HttpClient) {

  }

  authenticationService(username: String, password: String) {
    return this.http.get(`http://localhost:8080/api/v1/basicauth`,
      { headers: { authorization: this.createBasicAuthToken(username, password) } }).pipe(map((res) => {
        this.username = username;
        this.password = password;
        this.registerSuccessfulLogin(username, password);
      }));
  }

  createBasicAuthToken(username: String, password: String) {
    return 'Basic ' + window.btoa(username + ":" + password)
  }

  registerSuccessfulLogin(username, password) {
    sessionStorage.setItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME, username)
  }

  logout() {
    sessionStorage.removeItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME);
    this.username = null;
    this.password = null;
  }

  isUserLoggedIn() {
    let user = sessionStorage.getItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME)
    if (user === null) return false
    return true
  }

  getLoggedInUserName() {
    let user = sessionStorage.getItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME)
    if (user === null) return ''
    return user
  }
}

Add and Configure HttpInterceptor

We will be creating a new HttpInterceptor service called BasicAuthInterceptor Service. This service will check if the session has a valid username and basicauth String, then it will update the headers of all outgoing HTTP requests. We implement the interceptor by extending the HttpInterceptor.
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthenticationService } from './login/auth.service';

@Injectable()
export class HttpInterceptorService implements HttpInterceptor {

    constructor(private authenticationService: AuthenticationService) { }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (this.authenticationService.isUserLoggedIn() && req.url.indexOf('basicauth') === -1) {
            const authReq = req.clone({
                headers: new HttpHeaders({
                    'Content-Type': 'application/json',
                    'Authorization': `Basic ${window.btoa(this.authenticationService.username + ":" + this.authenticationService.password)}`
                })
            });
            return next.handle(authReq);
        } else {
            return next.handle(req);
        }
    }
}
Now we will register the created HTTPInterceptor using the app.module.ts by updating it in the provider section.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HelloWorldComponent } from './hello-world/hello-world.component';
import { MenuComponent } from './menu/menu.component';
import { LoginComponent } from './login/login.component';

import { FormsModule } from '@angular/forms';
import { LogoutComponent } from './logout/logout.component';
import { HttpInterceptorService } from './httpInterceptor.service';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
    HelloWorldComponent,
    MenuComponent,
    LoginComponent,
    LogoutComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    FormsModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpInterceptorService,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Logout Implementation

Let's create a logout component with the following command:
C:\angular-frontend\src\app> ng g c logout
CREATE src/app/logout/logout.component.html (21 bytes)
CREATE src/app/logout/logout.component.spec.ts (628 bytes)
CREATE src/app/logout/logout.component.ts (269 bytes)
CREATE src/app/logout/logout.component.css (0 bytes)

Add Logout Button to Menu Template

In the previous tutorial, we have created a Menu Html template and let's update it with logout button.
<header>
    <nav class="navbar navbar-expand-md navbar-dark bg-dark">
        <div><a href="https://www.javaguides.net" class="navbar-brand">JavaGuides</a></div>
        <ul class="navbar-nav navbar-collapse justify-content-end">
            <li>
                <a *ngIf="isLoggedIn" class="nav-link" href="/logout" (click)=handleLogout()>Logout</a>
            </li>
        </ul>
    </nav>
  </header>

Update MenuComponent

In the previous tutorial, we have created MenuComponent and let's update it for logout implementation. Once we will click on the logout button then the user stored in sessionStorage will be removed. 
Let's open menu.component.ts file and add below code to it -
import { Component, OnInit } from '@angular/core';
import { AuthenticationService } from '../login/auth.service';
import { ActivatedRoute, Router } from '@angular/router';

@Component({
  selector: 'app-menu',
  templateUrl: './menu.component.html',
  styleUrls: ['./menu.component.css']
})
export class MenuComponent implements OnInit {

  isLoggedIn = false;

  constructor(private route: ActivatedRoute,
    private router: Router,
    private authenticationService: AuthenticationService) { }

  ngOnInit() {
    this.isLoggedIn = this.authenticationService.isUserLoggedIn();
    console.log('menu ->' + this.isLoggedIn);
  }

  handleLogout() {
    this.authenticationService.logout();
  }
}

Update Routing Module

After user logout, a user should navigate to the login screen so let's update AppRoutingModule file as -
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HelloWorldComponent } from './hello-world/hello-world.component';
import { LoginComponent } from './login/login.component';

const routes: Routes = [
  {path: 'login', component: LoginComponent},
  {path: '', component: LoginComponent},
  {path: 'hello-world', component: HelloWorldComponent},
**  {path: 'logout', component: LoginComponent},**
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }

Demo

The below GIF image shows the demo of this application.
Username: javaguides
Password: password
Get source code of this tutorial on my GitHub Repository.

Comments

  1. Feel Official document of this article is Hard to understanding & practice without adequate preparation of terminology and knowledge reserve etc.

    ReplyDelete

Post a Comment