Angular,Spring Boot,Spring Data and Rest Example(CRUD)

Angular 8 Client-Side Architecture

With the release of Angular 8, there are many new features that have been introduced such as Ivy Preview, Web Workers, Lazy Loading, Differential Loading, etc. The new version requires TypeScript 3.4+ and Node 12+. We will be using Angular 8 at the client side.

We will have 4 different routes. The login route will be the welcome page that accepts username and password for login. On submit, the login API will be called and in response of the API, client receives a JWT token which is cached in the browser local cache so that any further HTTP request can be injected with this token in the header. We will have an interceptor implemented to perform this injection.

After login, the list route will be loaded which will again make a REST call to fetch user list and this list will be rendered in the browser. This screen will have options to add new user, edit and delete an existing user.

Spring Boot Server-Side Architecture

At the server-side, we will be using Spring Boot 2 to expose our REST endpoints. We will have Spring Security integrated with Spring Data to perform DB operations.

At the server side, we have a security filter defined that is responsible for intercepting all the requests to extract JWT token from the HTTP header and set the security context. You can follow this article to create a full-fledged JWT token-based authentication system using Spring Security.

After this the request will reach the controllers. We have seperate controllers defined for authentication related stuff and for user CRUD related stuff. The authentication related APIs are not secured but all the user related APIs are secured. The controller commands to the service layer to perform the business logic which makes DAO calls if required.

Generating Angular Project from CLI

Generating Spring Boot Project

Spring Boot Maven Dependency

Below is our final pom.xml if you simply want to add dependencies in your existing project. pom.xml

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
	</dependency>
	<dependency>
		<groupId>io.jsonwebtoken</groupId>
		<artifactId>jjwt</artifactId>
		<version>0.9.0</version>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>
</dependencies>

<properties>
	<java.version>1.8</java.version>
</properties>

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

Angular 8 Client Side Implementation

Angular 8 Components

Below are the lists of commands that we will execute to generate our components from CLI.

ng g component login
ng g component user/list-user
ng g component user/add-user
ng g component user/edit-user

Now, let us start implementing these components step by step.

Login Component

As you can see, the login component has a reactive login form that takes username and password input from the user. On the click of Login button, onSubmit() function is called. This function then calls the service component so that the username/password can be validated from the API. Even this request will be intercepted by our HTTP interceptor but the token will not be added in the header. 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>

login.component.ts

import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {Router} from "@angular/router";
import {ApiService} from "../service/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 => {
      debugger;
      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]
    });
  }

}

List User Component

Once the list view is rendered, an API call will be made to fetch user details and the same can be viewed in a tabular form. We have multiple buttons on this page to add, edit and delete any user. list-user.component.html

<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"></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></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>

There are many things happening here. All the action items implementation are here. On the click of Add button addUser() function will be invoked which will just navigate to add-user route.

On click of Edit button, editUser() function will be called. This method saves the selected user id in the local storage. Once, the edit component is rendered, this user id will be picked to fetch user details from the API and the add form is rendered in the editable format.

On click of the delete button, the selected rows will be removed from the table and an API call be made to remove this user from the DB too. list-user.component.ts

import { Component, OnInit , Inject} from '@angular/core';
import {Router} from "@angular/router";
import {User} from "../../model/user.model";
import {ApiService} from "../../service/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']);
  };
}

Add User Component

Add user component has a simple form to take input from the user and makes a HTTP call to save the user in the DB. Once, the user is added, user will be again routed to list-user route. 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 "../../service/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']);
      });
  }

}

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">Add</button>
  </form>
</div>

Edit User Component

Whenever, edit button is clicked, the selected user id is stored in the local storage and inside ngOnInit(), an API call is made to fetch the user by user id and the form is auto-populated. 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 {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {first} from "rxjs/operators";
import {User} from "../../model/user.model";
import {ApiService} from "../../service/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);
        });
  }

}

Angular 8 API Service Implementation

Below is the service class that makes HTTP calls to perform the CRUD operations. It uses HttpClient from @angular/common/http. As we discussed above, we have 2 different controllers in the server side and hence two different URL group to make HTTP request. 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);
  }

  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 8 Routing Configuration

The routing implementation in Angular 8 is similar to previous versions. app.routing.ts

import { RouterModule, Routes } from '@angular/router';
import {LoginComponent} from "./login/login.component";
import {AddUserComponent} from "./user/add-user/add-user.component";
import {ListUserComponent} from "./user/list-user/list-user.component";
import {EditUserComponent} from "./user/edit-user/edit-user.component";

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: 'add-user', component: AddUserComponent },
  { path: 'list-user', component: ListUserComponent },
  { path: 'edit-user', component: EditUserComponent },
  {path : '', component : LoginComponent}
];

export const routing = RouterModule.forRoot(routes);

Angular 8 HTTP Interceptors

The interceptor implements HttpInterceptor which intercepts all the HTTP request and token in the header for API authentication. At the time of login, there won't be any valid token present in the local cache hence, there is a condition check for the presence of token. interceptor.ts

import {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');
    if (token) {
      request = request.clone({
        setHeaders: {
          Authorization: 'Bearer ' + token
        }
      });
    }
    return next.handle(request);
  }
}

Spring Boot Server-Side Implementation

Spring Boot JWT Security

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 JwtAuthenticationFilter.java

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);
    }
}

Now let us define our usual spring boot security configurations.We have userDetailsService injected to fetch user credentials from database.

Here the annotation @EnableGlobalMethodSecurity enables method level security and you can annotate your method with annotations such as @Secured to provide role based authentication at method level. WebSecurityConfig.java

@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/*").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 Implementation

As discussed above, we have 2 different controllers - one is for authentication and another is to peform CRUD operation on User entity.

Following is the controller that is exposed to create token on user behalf and if you noticed in WebSecurityConfig.java we have configured this url to have no authentication so that user can generate JWT token with valid credentials. AuthenticationController.java

@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 ResponseEntity register(@RequestBody LoginUser loginUser) throws AuthenticationException {

        final Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginUser.getUsername(),
                        loginUser.getPassword()
                )
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);
        final User user = userService.findOne(loginUser.getUsername());
        final String token = jwtTokenUtil.generateToken(user);
        return ResponseEntity.ok(new AuthToken(token));
    }

}

Below is our controller class that has all the API implementation for the CRUD operation. Please let me know if you have any doubt with the code in the comment section. UserController.java

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 ApiResponse saveUser(@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 deleted successfully.", null);
    }



}

Spring Boot Service Implementation

UserServiceImpl.java

The service class has some business logic and it is the bridge between our controllers and DAOs.

@Transactional
@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 List getAuthority() {
		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", "username");
            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);
    }
}

Spring Data Implementation

Let us configure our DB connection parameters first. As we will be using MySQL in this app, below is our configuration properties. application.properties

spring.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 repo class. It extends CrudRepository that has all the implementations for CRUD operations. For a detailed integration of Spring Data JPA, you can visit my another article here. UserDao.java

@Repository
public interface UserDao extends CrudRepository {

    User findByUsername(String username);
}

Spring Boot Model Class

Below is our User model class the UserDto class. User.java

@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;

//setters and getters

UserDto.java

public class UserDto {

    private int id;
    private String firstName;
    private String lastName;
    private String username;
    private String password;
    private int age;
    private long salary;

//setters and getters

LoginUser.java

public class LoginUser {

    private String username;
    private String password;

//setters and getters

AuthToken.java

public class AuthToken {

    private String token;
    private String username;

//setters and getters

ApiResponse.java


public class ApiResponse {

    private int status;
    private String message;
    private Object result;

    public ApiResponse(int status, String message, Object result) {
        this.status = status;
        this.message = message;
        this.result = result;
    }

//setters and getters

Exception Handling at Server Side

There are 2 different configurations for exception handling in the server side. To handle REST exception, we use @ControllerAdvice and @ExceptionHandler in Spring MVC but these handler works if the request is handled by the DispatcherServlet. However, security-related exceptions occur before that as it is thrown by Filters. Hence, it is required to insert a custom filter (RestAccessDeniedHandler and RestAuthenticationEntryPoint) earlier in the chain to catch the exception and return accordingly.

Full explanation of handling exception in spring security can be found here in my previous article. JwtAuthenticationEntryPoint.java

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {

        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}

The exception handling part is out of scope for this article. In the production system, you will have your custom exception defined and you basically write your advice to handle those exceptions. Below implementation is just for a demo to get you started. Please let me know if you need a full-fledged solution to this.

@RestControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler(RuntimeException.class)
    public ApiResponse handleNotFoundException(RuntimeException ex) {
        ApiResponse apiResponse = new ApiResponse(400, "Bad request", null);
        return apiResponse;
    }

}

CORS Configuration for Spring Boot Angular

Modern browsers does not allow cross browser HTTP request and you will see error stating that Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

To enable CORS request at our server, we can add below filter. It will act as a global filter.


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() {}

}

Running the Application

We have command line runner integrated in the spring boot main class to add user during application start up.

@SpringBootApplication
public class Application {

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

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

    @Bean
    public CommandLineRunner init(UserDao userDao){
        return args -> {
            User user1 = new User();
            user1.setFirstName("Devglan");
            user1.setLastName("Devglan");
            user1.setSalary(12345);
            user1.setAge(23);
            user1.setUsername("devglan");
            user1.setPassword(passwordEncoder.encode("devglan"));
            userDao.save(user1);

            User user2 = new User();
            user2.setFirstName("John");
            user2.setLastName("Doe");
            user2.setSalary(4567);
            user2.setAge(34);
            user2.setUsername("john");
            user2.setPassword(passwordEncoder.encode("john"));
            userDao.save(user2);
        };
    }

}

Conclusion

In this article, we developed a full stack app with Angular 8 at the client-side, Spring Boot and Spring Data in the server-side and secured the REST endpoints with Spring security.

Last updated