In this article, we will be creating a sample Angular 7 application step by step from scratch and perform CRUD operations on a user entity. The back-end API will be exposed using Spring Boot REST and MySQL will be used for persistent storage. We will be using Angular CLI to generate our sample project and then we will add our all user components to it. We will have a login page with token-based authentication. We will be using routing configuration to switch in between the routes and ReactiveFormsModule for creating forms. We will also be implementing HttpInterceptor at client side to intercept all the API request to add auth token in the header. Hence, in short, we will be creating a full stack app using angular 7 and spring boot.
You can follow this article for Angular 8 integration with Spring Boot.
Angular 8 just got released this May and here is the article for Angular 8 CRUD example.
Angular 7 Release Highlights
Angular 7 got released this year in October with multiple new features such as CLI Prompts, Virtual Scroll, Drag and Drop, Angular Budgets and many more. From this release, Angular supports Node 10 and simultaneously maintains Node 8 support with TypeScript 3.1 and RxJS 6.3. A new extension from @angular/schematics called prompts has been introduced with the CLI that will now prompt developers when running common commands such as ng new my-app. Following is an example from the Angular Schematic for ng new:
"routing": { "type": "boolean", "description": "Generates a routing module.", "default": false, "x-prompt": "Would you like to add Angular routing?" },
Angular 7 Environment Setup
Both the CLI and generated project have dependencies that require Node 10 and hence let us first install it on our local machine. You can download the latest version of Node js from here - Node Js Official. I have node 10.13.0 installed on my local which includes npm 6.4.1. To update NPM, you can run the following command in the terminal.
npm i npm@latest -g
If u have an older @angular/cli version, then you can run following command to install the latest versions:
npm uninstall -g @angular/cli npm cache clean npm install -g @angular/cli
You can run below commands to check the versions on your local machine.
Other Interesting Posts Angular PayuMoney Integration Angular Server Side Rendering Example Angular Material Sidenav Example Spring Boot Angular Captcha Deploy Angular App on Nginx Angular 6 CRUD Example Angular JWT Authentication Angular Material App Example Typescript Basic Tutorial Spring Boot Angular Deployment Spring Boot Angular Example
Generating Angular 7 Project from CLI
Traverse to your folder of choice and execute following commands to generate and run Angular 7 app.
ng new my-dream-app cd my-dream-app ng serve
Now, you can open your browser and hit localhost:4200 to see the welcome page. Below is the screenshot.
Project Structure
Now, we can import the project in any IDE and check the the angular.json file to check the version of angular dependencies. Following are the different components that we will be creating. You can download the source from GitHub here.
ng g component login ng g component add-user ng g component edit-user ng g component list-user
Here, is the final project structure that we will creating by the end of this tutorial.
Angular 7 Login Component
For login component, we will have a reactive form that accepts username and password. On submit, /login
API is called which validates user credentials from DB and returns user token. We will be caching this token in local storage. The same token will be used in the subsequent request. Post login, user list page will be rendered. We have all validations in place to check the required fields are not empty. In a similar fashion, you can add multiple other validations such as character min-length or email validations.
Following is the implementation.
login.component.html<div class="row"> <div class="col-md-6 login-container"> <h2 style="margin: auto">Login </h2> <form [formGroup]="loginForm" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="username">Username:</label> <input type="text" class="form-control" formControlName="username" id="username" autocomplete="off"> <div class="error" *ngIf="loginForm.controls['username'].hasError('required') && loginForm.controls['username'].touched">Username is required</div> </div> <div class="form-group"> <label for="pwd">Password:</label> <input type="password" class="form-control" formControlName="password" id="pwd" autocomplete="off"> <div class="error" *ngIf="loginForm.controls['password'].hasError('required') && loginForm.controls['password'].touched">Password is required</div> </div> <button class="btn btn-success" [disabled]="loginForm.invalid">Login</button> <div *ngIf="invalidLogin" class="error"> <div>Invalid credentials.</div> </div> </form> </div> </div>
The ApiService has all the HTTP request implementation that makes HTTP request to our backend APIs.
login.component.tsimport { Component, OnInit } from '@angular/core'; import {FormBuilder, FormGroup, Validators} from "@angular/forms"; import {Router} from "@angular/router"; import {ApiService} from "../core/api.service"; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit { loginForm: FormGroup; invalidLogin: boolean = false; constructor(private formBuilder: FormBuilder, private router: Router, private apiService: ApiService) { } onSubmit() { if (this.loginForm.invalid) { return; } const loginPayload = { username: this.loginForm.controls.username.value, password: this.loginForm.controls.password.value } this.apiService.login(loginPayload).subscribe(data => { if(data.status === 200) { window.localStorage.setItem('token', data.result.token); this.router.navigate(['list-user']); }else { this.invalidLogin = true; alert(data.message); } }); } ngOnInit() { window.localStorage.removeItem('token'); this.loginForm = this.formBuilder.group({ username: ['', Validators.compose([Validators.required])], password: ['', Validators.required] }); } }
Angular 7 Service Implementation
We have 6 API implementation for CRUD operations including login API for user authentication. For now, let us assume our user endpoint is localhost:8080/users. Below is the implementation.All the API response has uniform response data format as below.
{ "status": 200, "message": "", "result": {} }api.service.ts
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import {User} from "../model/user.model"; import {Observable} from "rxjs/index"; import {ApiResponse} from "../model/api.response"; @Injectable() export class ApiService { constructor(private http: HttpClient) { } baseUrl: string = 'http://localhost:8080/users/'; login(loginPayload) : Observable<ApiResponse> { return this.http.post<ApiResponse>('http://localhost:8080/'+ 'token/generate-token', loginPayload).pipe( catchError(val => of(val))); }; } getUsers() : Observable<ApiResponse> { return this.http.get<ApiResponse>(this.baseUrl); } getUserById(id: number): Observable<ApiResponse> { return this.http.get<ApiResponse>(this.baseUrl + id); } createUser(user: User): Observable<ApiResponse> { return this.http.post<ApiResponse>(this.baseUrl, user); } updateUser(user: User): Observable<ApiResponse> { return this.http.put<ApiResponse>(this.baseUrl + user.id, user); } deleteUser(id: number): Observable<ApiResponse> { return this.http.delete<ApiResponse>(this.baseUrl + id); } }
Angular 7 User Components
List User
After a successfull login, list-user route will be loaded and getUsers()
will be invoked that will load the list of users and the user list will be shown in a tabular form. Each row will have button to either update or delete a user entry. Add button will open a new form to add a new user. On click of edit button, edit component will be loaded with user details auto populated where we can edit the user details and save the changes and click on delete button will instantly delete the user from DB and update the table.Following is the implementation.
<div class="col-md-6 user-container"> <h2 style="margin: auto"> User Details</h2> <button class="btn btn-danger" style="width:100px" (click)="addUser()"> Add User</button> <table class="table table-striped"> <thead> <tr> <th class="hidden">Id</th> <th>FirstName</th> <th>LastName</th> <th>UserName</th> <th>Age</th> <th>Salary</th> </tr> </thead> <tbody> <tr *ngFor="let user of users"> <td class="hidden">{{user.id}}</td> <td>{{user.firstName}}</td> <td>{{user.lastName}}</td> <td>{{user.username}}</td> <td>{{user.age}}</td> <td>{{user.salary}}</td> <td><button class="btn btn-success" (click)="deleteUser(user)"> Delete</button> <button class="btn btn-success" (click)="editUser(user)" style="margin-left: 20px;"> Edit</button></td> </tr> </tbody> </table> </div>
Post login, all the APIs are secured and hence requires token to perform authentication amd authorization at the server. Instead of duplicating code to add the token in every request, we have implemented HTTPInterceptor that intercepts all the HTTP request and adds required token in the header. The implementation is in the subsequent heading.
list-user.component.tsimport { Component, OnInit , Inject} from '@angular/core'; import {Router} from "@angular/router"; import {User} from "../model/user.model"; import {ApiService} from "../core/api.service"; @Component({ selector: 'app-list-user', templateUrl: './list-user.component.html', styleUrls: ['./list-user.component.css'] }) export class ListUserComponent implements OnInit { users: User[]; constructor(private router: Router, private apiService: ApiService) { } ngOnInit() { if(!window.localStorage.getItem('token')) { this.router.navigate(['login']); return; } this.apiService.getUsers() .subscribe( data => { this.users = data.result; }); } deleteUser(user: User): void { this.apiService.deleteUser(user.id) .subscribe( data => { this.users = this.users.filter(u => u !== user); }) }; editUser(user: User): void { window.localStorage.removeItem("editUserId"); window.localStorage.setItem("editUserId", user.id.toString()); this.router.navigate(['edit-user']); }; addUser(): void { this.router.navigate(['add-user']); }; }
Edit User
Whenever, user clicks on the edit button, the selected id is stored in the cache first and when edit route is loaded, the cached id is used to fetch user details from the server and auto populate the edit user form.
edit-user.component.html<div class="col-md-6 user-container"> <h2 class="text-center">Edit User</h2> <form [formGroup]="editForm" (ngSubmit)="onSubmit()"> <div class="hidden"> <input type="text" formControlName="id" placeholder="id" name="id" class="form-control" id="id"> </div> <div class="form-group"> <label for="username">User Name:</label> <input type="text" formControlName="username" placeholder="username" name="username" class="form-control" id="username" readonly="true"> </div> <div class="form-group"> <label for="firstName">First Name:</label> <input formControlName="firstName" placeholder="First Name" name="firstName" class="form-control" id="firstName"> </div> <div class="form-group"> <label for="lastName">Last Name:</label> <input formControlName="lastName" placeholder="Last name" name="lastName" class="form-control" id="lastName"> </div> <div class="form-group"> <label for="age">Age:</label> <input type="number" formControlName="age" placeholder="age" name="age" class="form-control" id="age"> </div> <div class="form-group"> <label for="salary">Salary:</label> <input type="number" formControlName="salary" placeholder="salary" name="salary" class="form-control" id="salary"> </div> <button class="btn btn-success">Update</button> </form> </div>edit-user.component.ts
import { Component, OnInit , Inject} from '@angular/core'; import {Router} from "@angular/router"; import {User} from "../model/user.model"; import {FormBuilder, FormGroup, Validators} from "@angular/forms"; import {first} from "rxjs/operators"; import {ApiService} from "../core/api.service"; @Component({ selector: 'app-edit-user', templateUrl: './edit-user.component.html', styleUrls: ['./edit-user.component.css'] }) export class EditUserComponent implements OnInit { user: User; editForm: FormGroup; constructor(private formBuilder: FormBuilder,private router: Router, private apiService: ApiService) { } ngOnInit() { let userId = window.localStorage.getItem("editUserId"); if(!userId) { alert("Invalid action.") this.router.navigate(['list-user']); return; } this.editForm = this.formBuilder.group({ id: [''], username: ['', Validators.required], firstName: ['', Validators.required], lastName: ['', Validators.required], age: ['', Validators.required], salary: ['', Validators.required] }); this.apiService.getUserById(+userId) .subscribe( data => { this.editForm.setValue(data.result); }); } onSubmit() { this.apiService.updateUser(this.editForm.value) .pipe(first()) .subscribe( data => { if(data.status === 200) { alert('User updated successfully.'); this.router.navigate(['list-user']); }else { alert(data.message); } }, error => { alert(error); }); } }
Add user
Similarly, we have add user implementation below.
add-user.component.html<div class="col-md-6 user-container"> <h2 class="text-center">Add User</h2> <form [formGroup]="addForm" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="username">User Name:</label> <input type="text" formControlName="username" placeholder="username" name="username" class="form-control" id="username"> </div> <div class="form-group"> <label for="password">Password:</label> <input type="password" formControlName="password" placeholder="password" name="password" class="form-control" id="password"> </div> <div class="form-group"> <label for="firstName">First Name:</label> <input formControlName="firstName" placeholder="First Name" name="firstName" class="form-control" id="firstName"> </div> <div class="form-group"> <label for="lastName">Last Name:</label> <input formControlName="lastName" placeholder="Last name" name="lastName" class="form-control" id="lastName"> </div> <div class="form-group"> <label for="age">Age:</label> <input type="number" formControlName="age" placeholder="age" name="age" class="form-control" id="age"> </div> <div class="form-group"> <label for="salary">Salary:</label> <input type="number" formControlName="salary" placeholder="salary" name="salary" class="form-control" id="salary"> </div> <button class="btn btn-success">Update</button> </form> </div>add-user.component.ts
import { Component, OnInit } from '@angular/core'; import {FormBuilder, FormGroup, Validators} from "@angular/forms"; import {Router} from "@angular/router"; import {ApiService} from "../core/api.service"; @Component({ selector: 'app-add-user', templateUrl: './add-user.component.html', styleUrls: ['./add-user.component.css'] }) export class AddUserComponent implements OnInit { constructor(private formBuilder: FormBuilder,private router: Router, private apiService: ApiService) { } addForm: FormGroup; ngOnInit() { this.addForm = this.formBuilder.group({ id: [], username: ['', Validators.required], password: ['', Validators.required], firstName: ['', Validators.required], lastName: ['', Validators.required], age: ['', Validators.required], salary: ['', Validators.required] }); } onSubmit() { this.apiService.createUser(this.addForm.value) .subscribe( data => { this.router.navigate(['list-user']); }); } }
Angular 7 HTTP Interceptor
The interceptor implements HttpInterceptor which intercepts all the HTTP request and token in the header for API authentication. Following is the implementation.
During login, there won't be any token present in the local cache hence, there is a condition check for presence of token.
interceptor.tsimport {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http"; import {Observable} from "rxjs/internal/Observable"; import {Injectable} from "@angular/core"; @Injectable() export class TokenInterceptor implements HttpInterceptor { intercept(request: HttpRequest, next: HttpHandler): Observable > { let token = window.localStorage.getItem('token'); debugger if (token) { request = request.clone({ setHeaders: { Authorization: 'Bearer ' + token } }); } return next.handle(request); } }
App Module Implementation
app.module.tsimport { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { LoginComponent } from './login/login.component'; import { AddUserComponent } from './add-user/add-user.component'; import { EditUserComponent } from './edit-user/edit-user.component'; import { ListUserComponent } from './list-user/list-user.component'; import {ApiService} from "./core/api.service"; import {HTTP_INTERCEPTORS, HttpClientModule} from "@angular/common/http"; import {ReactiveFormsModule} from "@angular/forms"; import {routing} from "./app.routing"; import {TokenInterceptor} from "./core/interceptor"; @NgModule({ declarations: [ AppComponent, LoginComponent, AddUserComponent, EditUserComponent, ListUserComponent ], imports: [ BrowserModule, routing, ReactiveFormsModule, HttpClientModule ], providers: [ApiService, {provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi : true}], bootstrap: [AppComponent] }) export class AppModule { }
Spring Boot Server Implementation
We have created multiple applications in my previous articles on spring boot security. All the examples are available here - Spring Boot Security Apps. Here, again we will be creating another spring boot app and integrate JWT with it for authentication and authorization. You can actually follow Spring Boot Security JWT article for step by step explanation as we may skip common explanation and implementations here and you can download the source from here.
First, let us head over to start.spring.io to generate a sample spring boot project and then import it into your workspace and then make changes in it to integrate JWT. Following will be our spring boot project structure.
JWT Token Validation
Following class extends OncePerRequestFilter that ensures a single execution per request dispatch. This class checks for the authorization header and authenticates the JWT token and sets the authentication in the context.Doing so will protect our APIs from those requests which do not have any authorization token.The configuration about which resource to be protected and which not can be configured in WebSecurityConfig.java
package com.devglan.config; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.SignatureException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Arrays; import static com.devglan.model.Constants.HEADER_STRING; import static com.devglan.model.Constants.TOKEN_PREFIX; public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { String header = req.getHeader(HEADER_STRING); String username = null; String authToken = null; if (header != null && header.startsWith(TOKEN_PREFIX)) { authToken = header.replace(TOKEN_PREFIX,""); try { username = jwtTokenUtil.getUsernameFromToken(authToken); } catch (IllegalArgumentException e) { logger.error("an error occured during getting username from token", e); } catch (ExpiredJwtException e) { logger.warn("the token is expired and not valid anymore", e); } catch(SignatureException e){ logger.error("Authentication Failed. Username or Password not valid."); } } else { logger.warn("couldn't find bearer string, will ignore the header"); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"))); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req)); logger.info("authenticated user " + username + ", setting security context"); SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(req, res); } }
Spring Boot Web Security Config
Below is the implementation of our security config that does not requires any authorization for endpoints token/*
and /signup
. Rest all the endpoints related to users are secured.
package com.devglan.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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; 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.web.authentication.UsernamePasswordAuthenticationFilter; import javax.annotation.Resource; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource(name = "userService") private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Autowired public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(encoder()); } @Bean public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception { return new JwtAuthenticationFilter(); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable(). authorizeRequests() .antMatchers("/token/*", "/signup").permitAll() .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); } @Bean public BCryptPasswordEncoder encoder(){ return new BCryptPasswordEncoder(); } }
Spring Boot Controller
We have 2 controller defined here. One for generating token i.e. sign up which is not secured and the other is usercontroller which requires authentication.
API Details
API Name - Token Generation URL - localhost:8080/token/generate-token Method - POST Header - Content-Type: application/json Body - { "username":"alex123", "password":"alex123" } Response : { "status": 200, "message": "success", "result": { "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE", "username": "alex123" } } API Name - List User URL - http://localhost:8080/users Method - Get Header - Content-Type: application/json Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE Response - { "status": 200, "message": "User list fetched successfully.", "result": [ { "id": 1, "firstName": "Alex", "lastName": "Knr", "username": "alex123", "salary": 3456, "age": 33 }, { "id": 2, "firstName": "Tom", "lastName": "Asr", "username": "tom234", "salary": 7823, "age": 23 }, { "id": 3, "firstName": "Adam", "lastName": "Psr", "username": "adam", "salary": 4234, "age": 45 } ] } API Name - Create User URL - http://localhost:8080/users Method - POST Header - Content-Type: application/json Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE Body - { "username":"test", "password":"test", "firstName":"test", "lastName":"test", "age":23, "salary":12345 } Response - { "status": 200, "message": "User saved successfully.", "result": { "id": 4, "firstName": "test", "lastName": "test"", "username": "test", "salary": 12345, "age": 23 } } API Name - Update User URL - http://localhost:8080/users/4 Method - PUT Header - Content-Type: application/json Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE Body - { "username":"test1", "password":"test1", "firstName":"test1", "lastName":"test1", "age":24, "salary":12345 } Response - { "status": 200, "message": "User updated successfully.", "result": { "id": 0, "firstName": "test1", "lastName": "test1", "username": "test1", "password": "test1", "age": 24, "salary": 12345 } } API Name - Delete User URL - http://localhost:8080/users/4 Method - DELETE Header - Content-Type: application/json Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE Response - { "status": 200, "message": "User deleted successfully.", "result": null }AuthenticationController.java
package com.devglan.controller; import com.devglan.config.JwtTokenUtil; import com.devglan.model.*; import com.devglan.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/token") public class AuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserService userService; @RequestMapping(value = "/generate-token", method = RequestMethod.POST) public ApiResponseUserController.javaregister(@RequestBody LoginUser loginUser) throws AuthenticationException { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword())); final User user = userService.findOne(loginUser.getUsername()); final String token = jwtTokenUtil.generateToken(user); return new ApiResponse<>(200, "success",new AuthToken(token, user.getUsername())); } }
package com.devglan.controller; import com.devglan.model.ApiResponse; import com.devglan.model.User; import com.devglan.model.UserDto; import com.devglan.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import java.util.List; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; @PostMapping public ApiResponsesaveUser(@RequestBody UserDto user){ return new ApiResponse<>(HttpStatus.OK.value(), "User saved successfully.",userService.save(user)); } @GetMapping public ApiResponse > listUser(){ return new ApiResponse<>(HttpStatus.OK.value(), "User list fetched successfully.",userService.findAll()); } @GetMapping("/{id}") public ApiResponse
getOne(@PathVariable int id){ return new ApiResponse<>(HttpStatus.OK.value(), "User fetched successfully.",userService.findById(id)); } @PutMapping("/{id}") public ApiResponse update(@RequestBody UserDto userDto) { return new ApiResponse<>(HttpStatus.OK.value(), "User updated successfully.",userService.update(userDto)); } @DeleteMapping("/{id}") public ApiResponse delete(@PathVariable int id) { userService.delete(id); return new ApiResponse<>(HttpStatus.OK.value(), "User fetched successfully.", null); } }
Spring Boot Service implementation
The service implementation is very simple which communicates with spring data JPA to fetch rows from DB. One thing to remeber here is that we are saving Bcrypt encoded password in the DB.
UserService.javapackage com.devglan.service.impl; import com.devglan.dao.UserDao; import com.devglan.model.User; import com.devglan.model.UserDto; import com.devglan.service.UserService; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @Service(value = "userService") public class UserServiceImpl implements UserDetailsService, UserService { @Autowired private UserDao userDao; @Autowired private BCryptPasswordEncoder bcryptEncoder; public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userDao.findByUsername(username); if(user == null){ throw new UsernameNotFoundException("Invalid username or password."); } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthority()); } private ListgetAuthority() { return Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")); } public List findAll() { List list = new ArrayList<>(); userDao.findAll().iterator().forEachRemaining(list::add); return list; } @Override public void delete(int id) { userDao.deleteById(id); } @Override public User findOne(String username) { return userDao.findByUsername(username); } @Override public User findById(int id) { Optional optionalUser = userDao.findById(id); return optionalUser.isPresent() ? optionalUser.get() : null; } @Override public UserDto update(UserDto userDto) { User user = findById(userDto.getId()); if(user != null) { BeanUtils.copyProperties(userDto, user, "password"); userDao.save(user); } return userDto; } @Override public User save(UserDto user) { User newUser = new User(); newUser.setUsername(user.getUsername()); newUser.setFirstName(user.getFirstName()); newUser.setLastName(user.getLastName()); newUser.setPassword(bcryptEncoder.encode(user.getPassword())); newUser.setAge(user.getAge()); newUser.setSalary(user.getSalary()); return userDao.save(newUser); } }
Model Class Implementation
User.javapackage com.devglan.model; import com.fasterxml.jackson.annotation.JsonIgnore; import javax.persistence.*; @Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private int id; @Column private String firstName; @Column private String lastName; @Column private String username; @Column @JsonIgnore private String password; @Column private long salary; @Column private int age; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public long getSalary() { return salary; } public void setSalary(long salary) { this.salary = salary; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }UserDto.java
package com.devglan.model; public class UserDto { private int id; private String firstName; private String lastName; private String username; private String password; private int age; private int salary; }
Following is the DB config we are using.
application.propertiesspring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=create-drop spring.user.datasource.driver-class-name=com.mysql.jdbc.Driver spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
Below is the DB scripts.
CREATE TABLE user(id INT NOT NULL AUTO_INCREMENT,age INT,firstName VARCHAR(255),lastName VARCHAR(255),password VARCHAR(255),salary BIGINT,username VARCHAR(255),PRIMARY KEY (id))ENGINE=MyISAM; INSERT INTO User (id, firstname, lastname, username, password, salary, age) VALUES (1, 'Alex','Knr', 'alex123','$2a$04$4vwa/ugGbBVDvbWaKUVZBuJbjyQyj6tqntjSmG8q.hi97.xSdhj/2', 3456, 33); INSERT INTO User (id, firstname, lastname, username, password, salary, age) VALUES (2, 'Tom', 'Asr', 'tom234', '$2a$04$QED4arFwM1AtQWkR3JkQx.hXxeAk/G45NiRd3Q4ElgZdzGYCYKZOW', 7823, 23); INSERT INTO User (id, firstname, lastname, username, password, salary, age) VALUES (3, 'Adam', 'Psr', 'adam', '$2a$04$WeT1SvJaGjmvQj34QG8VgO9qdXecKOYKEDZtCPeeIBSTxxEhazNla', 4234, 45);
Adding CORS with Spring Boot Angular
Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. Since, we have server running at localhost:8080 and angular running at localhost:4200, we see error as No 'Access-Control-Allow-Origin'. To avoid this error, we can add below filter in the spring project.
package com.devglan.config; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; public class CORSFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { System.out.println("Filtering on..........................................................."); HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers"); chain.doFilter(req, res); } public void init(FilterConfig filterConfig) {} public void destroy() {} }
Conclusion
This is pretty much implementation of a full stack app using Angular 7 and Spring Boot with JWT authentication. I know you need the source code now. You can download it from this link for the same. It has both the server and client project.If you want to add anything else then please let me know in the comments below.