In this tutorial:
1. We will create a Spring boot project with a simple REST API
2. We will add spring security to our spring boot project to secure REST API
3. We will create an Angular 12 App
4. We will implement login and logout features in the Angular 9 App.
5. We will have a demo
6. Conclusion
Learn full stack app development using Spring boot and Angular 9 at
Spring Boot + Angular 9 CRUD Example Tutorial
1. Creating a Simple REST API using Spring Boot
http://localhost:8080/greeting
{"id":1,"content":"Hello, World!"}
http://localhost:8080/greeting?name=User
{"id":1,"content":"Hello, User!"}
1.1. Create a Spring Boot Application
1.2. Add maven dependencies
<?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-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>
1.3. Greeting.java
package net.javaguides.springboot.Springboothelloworldapplication;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
1.4. Create a resource controller - GreetingController.java
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import net.javaguides.springboot.helloworldapp.bean.Greeting;
@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("/api/v1")
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@RequestMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
1.5. SpringbootHelloworldApplication.java
package net.javaguides.springboot.Springboothelloworldapplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootHelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootHelloworldApplication.class, args);
}
}
1.6. Running the Application
- From the root directory of the application and type the following command to run it -
$ mvn spring-boot:run
- From your IDE, run the SpringbootHelloworldApplication.main() method as a standalone Java class that will start the embedded Tomcat server on port 8080 and point the browser to http://localhost:8080/.
Test REST API
Before developing an Angular client, let's test this web service.Now that the service is up, visit http://localhost:8080/api/v1/greeting, where you see:Provide a name query string parameter with http://localhost:8080/greeting?name=User. Notice how the value of the content attribute changes from "Hello, World!" to "Hello User!":
2. Security implementation in Spring boot application
2.1 Add spring-boot-starter-security Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<?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>
2.2 Default Security Setup
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
c8be15de-4488-4490-9dc6-fab3f91435c6
Default username - user
Default password -
c8be15de-4488-4490-9dc6-fab3f91435c6
2.3 Configure standard user and password
spring.security.user.name=javaguides
spring.security.user.password=password
2.3 Configure WebSecurityConfigurerAdapter
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();
}
}
2.4 Define AuthenticationBean and BasicAuthController
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);
}
}
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");
}
}
2.5 Testing above Security Implementation using Postman Rest Client
3. Creating Angular App
http://localhost:8080/api/v1/greeting
C:\Angular>node -v
v10.15.3
C:\Angular>npm -v
6.9.0
3.1 Install the latest version of Angular CLI
npm install -g @angular/cli
C:\angular>ng --version
3.2 Create Angular Application using Angular CLI
ng new angular-frontend
3.3 Create Angular Service & Components
- ng g s hello-world
– ng g c hello-world
– ng g c menu
– ng generate class Message
3.4 Angular Project Structure
3.5 Integrate Bootstrap with Angular
npm install bootstrap jquery --save
...
"styles": [
"src/styles.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js"
]
...
/* You can add global styles to this file, and also import other style files */
@import url(https://unpkg.com/bootstrap@4.1.0/dist/css/bootstrap.min.css)
3.6 hello-world.service.ts -> HelloWorldService
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MessageModel } from '../model/Message';
@Injectable({
providedIn: 'root'
})
export class HelloWorldService {
constructor(private http: HttpClient) {
}
executeHelloWorldService() {
return this.http.get<MessageModel>('http://localhost:8080/api/v1/greeting');
}
}
3.7 Model -> Message.ts
export class MessageModel {
id: number;
content: string;
constructor(private _id: number, public message: string) {
this.id = _id;
this.content = message;
}
}
3.8 hello-world.component.ts -> HelloWorldComponent
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { HelloWorldService } from '../service/hello-world.service';
@Component({
selector: 'app-hello-world',
templateUrl: './hello-world.component.html',
styleUrls: ['./hello-world.component.css']
})
export class HelloWorldComponent implements OnInit {
welcomeMessage = '';
constructor(private route: ActivatedRoute,
private router: Router, private helloWorldService: HelloWorldService) { }
ngOnInit() {
this.helloWorldService.executeHelloWorldService().subscribe((res) => {
this.welcomeMessage = res.content;
});
}
}
3.9 hello-world.component.html -> HelloWorldComponent
<div class="container">
Message from server -> <h1>{{this.welcomeMessage}}</h1>
</div>
3.20 menu.component.ts -> MenuComponent
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.css']
})
export class MenuComponent implements OnInit {
welcomeMessage = 'Test';
constructor() { }
ngOnInit() {
}
}
3.11 menu.component.html -> MenuComponent
<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">
<li><a class="nav-link" href="/hello-world">Hello World Tab</a></li>
</ul>
</nav>
</header>
3.12 app.module.ts -> AppModule
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HelloWorldService } from './service/hello-world.service';
import { MenuComponent } from './menu/menu.component';
import { HelloWorldComponent } from './hello-world/hello-world.component';
@NgModule({
declarations: [
AppComponent,
MenuComponent,
HelloWorldComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
],
providers: [
HelloWorldService,
],
bootstrap: [AppComponent]
})
export class AppModule { }
3.13 app-routing.module.ts -> AppRoutingModule
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HelloWorldComponent } from './hello-world/hello-world.component';
const routes: Routes = [
{path: '', component: HelloWorldComponent},
{path: 'hello-world', component: HelloWorldComponent},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
3.14 app.component.ts -> AppComponent
The app.component.ts file defines the logic for the app's root component, named AppComponent:import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'frontend-spring-boot-angular-hello-world-example';
}
3.15 app.component.html -> AppComponent
<app-menu></app-menu>
<router-outlet></router-outlet>
3.16 Running Angular 9 Application
ng serve
ng serve --port 4204
4. Adding Basic Login and Logout implementation in Angular Application
4.1 Login Authentication Implementation
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)
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)
4.2 LoginComponent
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;
});
}
}
4.3 Login HTML Template
<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>
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 { }
4.4 AuthenticationService
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
}
}
4.5 Add and Configure 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);
}
}
}
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 { }
4.6 Logout Implementation
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)
4.7 Add Logout Button to Menu Template
<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>
4.8 Update MenuComponent
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();
}
}
4.9 Update 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 { }
5. Demo
Username: javaguides
Password: password
6. Conclusion
In this tutorial:
- We created a Spring boot project
- We created a simple REST API
- We have added spring security to our spring boot project to secure REST API
- We created the Angular App
- We have implemented login and logout features in the Angular App.
- Finally, we had a demo
I implemented in my angular project this auth. but when loading the system logout
ReplyDelete@CrossOrigin(origins = "http://localhost:4200")
ReplyDelete@RestController
@RequestMapping("/api/v1")
Para funcionar certo, faltou acrescentar essas anotações no GreetingController, também.