Search code examples
angularspring-boothttp-status-code-403spring-restcontrollerangular-httpclient

Simple POST request from Angular client to Spring boot server gives status 403 error


I get this error 403 when I try to perform a simple POST request to my Spring Boot server from Angular client.

Following code is the HTML template...

HTML Template

<!-- Create book html -->
<div class="content-container">
  <div class="content-area">
  
    <!-- Back button -->
    <div> 
      <button class="btn btn-danger btn-block" (click)="back()">Back</button>
    </div>

    <p style="font-weight: bold; color: green;">Please enter all the details in-order to enter a new book!!!</p><br/>

    <p>{{book | json}}<p>

    <form (ngSubmit)="onCreate()">
      <!-- Book name -->
      <label for="name"><b>Book name:</b></label><br/>
      <input type="text" name="name" class="formField" size="100" placeholder="Ex: Bram Stoker's Dracula" [(ngModel)]="book.name"/><br/><br/>
    
      <!-- Author ID -->
      <label for="authorid"><b>Author ID:</b></label><br/>
      <input type="text" name="authorid" class="formField" size="50" placeholder="Ex: 100015" [(ngModel)]="book.authorID"/><br/><br/>

      <!-- Publication ID -->
      <label for="publicationid"><b>Publication ID:</b></label><br/>
      <input type="text" name="publicationid" class="formField" size="50" placeholder="Ex: 200015" [(ngModel)]="book.publicationID"/><br/><br/>
      
      <!-- Publication date -->
      <label for="publicationdate"><b>Publication Date:</b></label><br/>
      <input type="date" name="publicationdate" class="formField" [ngModel]="book.publicationDate | date:'yyyy-MM-dd'" (ngModelChange)="book.publicationDate = $event"/><br/><br/>
      
      <!-- Description -->
      <label for="description"><b>Book Description:</b></label><br/>
      <textarea name="description" class="formField" rows="3" cols="100" placeholder="Describe about the book here..." [(ngModel)]="book.description"></textarea><br/><br/>

      <!-- Edition -->
      <label for="edition"><b>Book Edition:</b></label><br/>
      <input type="text" name="edition" class="formField" size="50" placeholder="Ex: 5" [(ngModel)]="book.edition"/><br/><br/>

      <!-- Category -->
      <label for="category"><b>Book Category:</b></label><br/>
      <input type="text" name="category" class="formField" size="50" placeholder="Ex: 3" [(ngModel)]="book.category"/><br/><br/>

      <!-- Rating -->
      <label for="rating"><b>Book Rating:</b></label><br/>
      <input type="text" name="rating" class="formField" size="50" placeholder="Ex: 10" [(ngModel)]="book.rating"/><br/><br/>
      
      <div style="text-align:center; width:100%;">
         <button type="submit" class="btn btn-primary btn-block">Create</button>
     </div>
    </form>

  </div>
</div>

Following code is the component...

Component

import { Component, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { Book } from '../services/data';
import { Router } from '@angular/router';
import { BookService } from '../services/book.service';

//Create book componenet 

@Component({
  selector: 'app-createbook',
  templateUrl: './createbook.component.html',
  styleUrls: ['./createbook.component.css']
})
export class CreatebookComponent implements OnInit {
  //Attributes
  book = {
    bookID: 0,
    name: '',
    authorID: 0,
    publicationID: 0,
    publicationDate: new Date,
    description: '',
    edition: 0,
    category: 0,
    rating: 0
  };

  //Constructor of the CreatebookComponent. 
  constructor(private location: Location, private router: Router, private bookService: BookService) { }

  //Component life cycle hook. Executes once componenet initiated. 
  ngOnInit(): void { }
  
  //Executes on book create. 
  onCreate() {
      this.bookService.createBook(this.book);
  }

  //Responsible for handling the back mechanism. 
  back() {
      this.location.back();
  }

}

Following code is the service class which performs the POST request...

Service

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Router } from '@angular/router';
import { Book } from './data';

//Service for books in library client 

@Injectable()
export class BookService{
  //Attributes 
  recentBooks: Array<Book> = []; 
  createdBook: Book; 

  //Constructor of BookService 
  constructor(private httpClient: HttpClient, private router: Router) { }
  
  //Return the recent book array. 
  get(): Book[] {
      return this.recentBooks;
  }
  
  //Return recent books. 
  getRecentBooks() {
      return this.httpClient.get<Array<Book>>("http://localhost:8080/book/recents");//.subscribe(book => this.recentBooks=book);
      //return this.recentBooks;
  }
  
  //Responsible for creating given book. 
  createBook(theBook: any): void {
      this.httpClient.post("http://localhost:8080/book/create",
                           theBook,
                          { headers: new HttpHeaders().set("Content-type", "application/json") }).subscribe(result => this.redirectHome());
  }
  
  //Redirect to home page 
  redirectHome() {
      this.router.navigate(['/home']);
  }
  
}

Following code shows the spring boot REST controller code...

REST Controller

package com.example.LibraryServer.RestControllers;

import com.example.LibraryServer.Entities.Book;
import com.example.LibraryServer.Repositories.BookRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * REST controller for books.
 */
@Slf4j //Lombok annotation for logger
@RestController
@RequestMapping(path = "/book", produces = "application/json")
@CrossOrigin(origins = "http://localhost:4200") //Cross-origin resource sharing
public class BookController {

    private final BookRepository bookRepo;

    /**
     * Constructor of BookController.
     * @param theBookRepository BookRepository
     */
    @Autowired
    public BookController(BookRepository theBookRepository) {
        this.bookRepo = theBookRepository;
    }

    /**
     * Responsible for returning recent books.
     * Returns maximum 12 most recently created books.
     * @return Iterable<Book>
     */
    @GetMapping("/recents")
    public Iterable<Book> recentBooks() {
        log.info("*****LibrarySystem LOGGER***** @GetMapping(/recents) -> REST endpoint executed");
        return bookRepo.recent();
    }

    /**
     * Responsible for returning the book for the given book ID.
     * @param theBookID int
     * @return ResponseEntity<Book>
     */
    @GetMapping("/{id}")
    public ResponseEntity<Book> getBook(@PathVariable("id") int theBookID) {
        log.info("*****LibrarySystem LOGGER***** @GetMapping(/{id}) -> REST endpoint executed");
        Book book = bookRepo.getByID(theBookID);
        if (book != null) {
            return new ResponseEntity<>(book, HttpStatus.OK);
        }
        else {
            return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
        }
    }

    /**
     * Responsible for deleting the book for the given book ID.
     * @param theBookID int
     */
    @DeleteMapping("/{id}")
    public void delete(@PathVariable("id") int theBookID) {
        log.info("*****LibrarySystem LOGGER***** @DeleteMapping(/{id}) -> REST endpoint executed");
        bookRepo.delete(theBookID);
    }

    /**
     * Responsible for persisting the given book.
     * @param theBook Book
     * @return Book
     */
    @PostMapping(path = "/create", consumes = "application/json")
    @ResponseStatus(HttpStatus.CREATED)
    public Book save(@RequestBody Book theBook) {
        log.info("*****LibrarySystem LOGGER***** @PostMapping -> REST endpoint executed");
        return bookRepo.save(theBook);
    }

}

Error enter image description here

Request doesn't receive by the Spring boot server.

What is wrong with my code?


Solution

  • I had to do one change in my security configuration.
    I did disable CSRF(Cross-Site Request Forgery) and that was blocking my POST request.

    package com.example.LibraryServer.Security;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.CorsConfigurationSource;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    
    import java.util.Arrays;
    
    /**
     * Class responsible for security configurations.
     */
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * Responsible for user security configuration.
         * Overridden from WebSecurityConfigurerAdapter level.
         * @param theHttpSecurity HttpSecurity
         * @throws Exception - Exception upon security configuration.
         */
        @Override
        protected void configure(HttpSecurity theHttpSecurity) throws Exception {
            theHttpSecurity.authorizeRequests()
                    .antMatchers("/**").access("permitAll")
                    .and().cors().and().csrf().disable();
        }
    
        /**
         * Responsible for configuring user-store.
         * Overridden from WebSecurityConfigurerAdapter level.
         * @param theAuthentication AuthenticationManagerBuilder
         * @throws Exception - Exception upon user store creation.
         */
        @Override
        public void configure(AuthenticationManagerBuilder theAuthentication) throws Exception {
            theAuthentication.inMemoryAuthentication()
                    .withUser("sankalpa")
                    .password("{noop}123")
                    .authorities("ROLE_USER");
        }
    
        @Bean
        CorsConfigurationSource corsConfigurationSource() {
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
            configuration.setAllowedMethods(Arrays.asList("*"));
            configuration.setAllowedHeaders(Arrays.asList("*"));
            configuration.setAllowCredentials(true);
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", configuration);
            return source;
        }
    
    }