spring

Solving HTTP 403 Forbidden Error in a Spring Boot Post Request

This article aims to provide a step-by-step guide on Solving HTTP 403 Forbidden Error in a Spring Boot Post Request. It covers various aspects, including authorization and authentication mechanisms, CSRF protection configuration, request mapping, and endpoint security, testing using the cURL command from a terminal, and, applying the appropriate fixes.

1. Introduction

When developing a Spring boot application, developers could come across the HTTP 403 forbidden error message when performing an HTTP POST request to a resource/endpoint. Some typical reasons for this occurrence could include when access to a resource or endpoint is being denied by a server due to insufficient permissions, custom authorization rules being implemented in the application, or the in-built CSRF (Cross-Site Request Forgery) protection enabled from Spring Security.

1.1 Explanation of the HTTP status code 403 – Forbidden

The 403 forbidden error is a standard HTTP status code that indicates that a server understands a client’s request but refuses to execute it. A 403 error means that a server is denying access to a requested resource or web page.

1.2 Common Scenarios Leading to a 403 Error

Below are some common scenarios that can lead to the HTTP 403 forbidden error in a Spring Boot POST request.

  • Insufficient Privileges: Role-based access control (RBAC) is a widely used approach that is used to restrict resource access to authorized users. The 403 error may occur if a user lacks the necessary privilege to perform a POST request on a resource.
  • Authentication Failure: Some web applications or APIs require users to log in before accessing certain resources. If the login credentials are missing or invalid, a server will respond with a 403 error, denying access to the resource. Also, if some custom authorization rules have been implemented to deny access to a requested resource, a 403 forbidden error may be returned.
  • CRSF Protection: Spring Security provides built-in Cross-Site Request Forgery (CSRF) protection that prevents unauthorized POST requests from external sources and enables it by default. A 403 error may be returned If a CSRF token is missing or invalid in a POST request.
  • IP blocking: A Server can be configured to block specific IP addresses for various reasons, such as to restrict access to certain geographical regions or suspected malicious activity. If your IP address is blocked, you’ll receive a 403 error when trying to access a resource.

2. Build a REST API with Spring Boot

In order to demonstrate how to solve the HTTP 403 forbidden error message, we will be building a Spring Boot application and using Spring Security, we would add authentication, authorization, and other security features to protect the resources in the application.

2.1 Create a Spring boot project

Firstly, we will use the Spring Initialzr to generate the base for our project with the following configuration:

Fig 2.0 Spring boot initialzr configuration
Fig 2.0 Spring boot initialzr configuration
  • Project – Maven
  • Language – Java
  • Spring Boot Version – 2.7.13
  • Group – com.javacodegeeks.example
  • Artifact – employee-service
  • Packaging – Jar
  • Java Version – 11
  • Dependencies – Spring Web and Spring Security

The following dependencies are added to the project pom.xml as shown below

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<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>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

Next, Click Generate and expand the.zip file after downloading, and open the project in your IDE. Update the application.properties file located in src/main/resources folder and add the following property server.port=8081. This changes the default server port from 8080 to 8081.

Next, we create a Employee.java class that would be used as our data model. Note that getter, setter and toString methods have been removed for brevity.

public class Employee {

    private int id;
    private String name;
    private String department;

    public Employee() {
    }

    public Employee(int id, String name, String department) {
        this.id = id;
        this.name = name;
        this.department = department;
    }

}

Next, create a EmployeeService.java, a class that would be used as our data repository to manage employees and populates the application with some default data.

@Component
public class EmployeeService {

    public EmployeeService() {
    }

    static List employeeList = new ArrayList();

    static {
        employeeList.add(new Employee(1, "Thomas", "Sales"));
        employeeList.add(new Employee(2, "Bill", "Account"));
        employeeList.add(new Employee(3, "Fela", "Information Technology"));
    }

    public List getAllEmployees() {
        return employeeList;
    }

    public int addEmployee(Employee employee) {
        int newId = employeeList.size() + 1;
        employee.setId(newId);
        employeeList.add(employee);
        return newId;
    }       
}

Next, create a EmployeeController.java. A class that will be used to expose our Employee.java class as a resource in the application.

@RestController
@RequestMapping("/api")
public class EmployeeController {

    private EmployeeService service;

    public EmployeeController(EmployeeService service) {
        this.service = service;
    }

    @GetMapping("/employees")
    Collection employees() {
        return service.getAllEmployees();
    }

    @PostMapping("/employee")
    ResponseEntity createEmployee(@RequestBody Employee employee) throws URISyntaxException {
        int newEmployeeId = service.addEmployee(employee);       
        return ResponseEntity.created(new URI("/api/employee/" + newEmployeeId))
                .body(employee);
    }
}

In the above code, we have added a @RestController annotation to the class that indicates that the class is a controller and the methods in it are capable of handling HTTP requests and producing HTTP responses. We have also added an @PostMapping annotation to the createEmployee method that indicates that the server would be able to handle a HTTP POST request to this method.

When we added Spring Security to the application, all endpoints were secured by default. Next, we will define our custom security configuration by creating a SecurityConfig.java class to accept unrestricted access to all endpoints. This is done by registering a SecurityFilterChain bean to accept all incoming requests without requiring any form of authentication as shown below.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .permitAll();
          
        return http.build();
    }
}

To test the REST API service, open a terminal and navigate to the project directory and type the following command

./mvnw spring-boot:run

After running the above command, the application will start and be available on port 8081. The application can then be accessible on a web browser by navigating to http://localhost:8081/api/employees or by issuing the curl command curl -X GET -i http://localhost:8081/api/employees on a terminal. The screenshot below shows the data and output returned from a terminal

Fig 2.1 Output of data returned from REST API
Fig 2.1 Output of data returned from REST API

3. Solving the HTTP 403 Forbidden Error in a Spring Boot POST Request

In this section, we will be providing solutions to the common scenarios that lead to the HTTP 403 forbidden error in a Spring Boot POST request listed in section 1.2 of this article.

3.1 Verify CSRF Protection Configuration

Here, we will take a look at what CSRF is, explain CSRF token and protection, and show how to disable CSRF configuration in Spring boot applications.

3.1.1 What is CSRF

CSRF is an acronym for Cross-Site Request Forgery. Cross-site request forgery is a web security vulnerability that allows authenticated users to perform malicious actions on web applications.

3.1.1 Understanding CSRF Token and Protection

A CSRF token is a unique randomly generated and unpredictable value that is generated by a server-side application and shared with a client. CSRF protection prevents unauthorized HTTP POST requests from external sources. If a CSRF token is invalid or missing when making a POST request, Spring Security may respond with a 403 forbidden error.

3.1.2 Verifying and Configuring CSRF Protection in a Spring Boot Application

Spring Security provides built-in Cross-Site Request Forgery (CSRF) protection enabled by default. To verify CSRF configuration in the application, we would have to make an HTTP POST request to the endpoint http://localhost:8081/api/employee to add a new employee.

3.1.2.1 Test with cURL to Send a POST Request and Observe the Response

Now let’s make an HTTP POST request to the endpoint at http://localhost:8081/api/employee to observe the response using the cURL command below

curl -X POST -i http://localhost:8081/api/employee -H 'Content-Type: application/json' -d '{"name":"Omozegie", "department": "Human Resources"}'

Performing the above request results in an HTTP 403 Forbidden error as shown in the figure below

Output from Issuing a POST request showing 403 forbidden error
Fig 3.1 Output from Issuing a POST request showing 403 forbidden error

To solve the 403 “Forbidden” error message, the built-in CSRF protection in a Spring Boot application has to be disabled. The method proposed here assumes that users won’t be using the application through a web browser else to handle a 403 error in a Spring Boot POST request with CSRF enabled, make sure your POST request includes the appropriate CSRF token which can be obtained from the server and included in the request header or body.

To disable the default CSRF protection, update SecurityConfig.java class to add .and().csrf().disable() method to the SecurityFilterChain bean as shown below. Note that this method is usually not recommended in a production environment.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .permitAll().and()
            .csrf()
            .disable();
          
        return http.build();
    }

}

After disabling the in-built CSRF protection and making the same POST request to the server, the server returns with an HTTP 201 message indicating that the POST request was successful as the output shows in the figure below.

Fig 3.2 Output of a Successful POST request
Fig 3.2 Output of a Successful POST Request

3.2 Review Authentication and Authorization mechanisms

Some web applications and REST APIs require authentication before access to certain resources can be granted. Not providing authentication credentials or providing incorrect authentication details can lead to a 403 forbidden error when making a Spring boot HTTP POST request. To provide authentication credentials and enforce access control, let us first modify the SecurityConfig.java class to configure an in-memory user store to add two users. We will also modify the SecurityFilterChain bean to accept all incoming authentication requests to the server before granting access. The modified SecurityConfig.java class is shown below.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private static final String ADMIN = "ADMIN";
    private static final String USER = "USER";

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/api/employees/**").hasRole(USER)
                .antMatchers("/api/employee/**").hasRole(ADMIN)
                .anyRequest().authenticated()
                .and().httpBasic()
                .and().formLogin()
                .and().logout()
                .and().csrf().disable();

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User
                .withUsername("thomas")
                .password(encoder().encode("paine"))
                .roles(ADMIN).build());
        manager.createUser(User
                .withUsername("bill")
                .password(encoder().encode("withers"))
                .roles(USER).build());
        return manager;
    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

}

In the code above, We have configured Spring Security to restrict access to specific URL patterns using roles also known as Role Based Access Control (RBAC). The tables below provide an explanation and summary of the code. We also added two users to the in-memory store with username thomas and password paine having an ADMIN role and another user with a username bill and password withers having a USER role.

URL PatternDescription
.antMatchers("/api/employees/**").hasRole(USER)Restricts the URL pattern /api/employees/** to users with the USER role.
.antMatchers("/api/employee/**").hasRole(ADMIN)Restricts the URL pattern /api/employee/** to users with the ADMIN role.
Table 1.0 Showing and Explaining Role Based Access Control used in the application
User.withUsername("thomas")
.password(encoder().encode("paine"))
.roles(ADMIN)
Defines a user with username thomas, password paine, having an ADMIN role.
User
.withUsername("bill")
.password(encoder().encode("withers"))
.roles(USER)
Defines a user with username bill, password withers, and having a USER role.
Table 1.1 describe users and their roles configured in the application

Now, if we make a POST Request to the http://localhost:8081/api/employee endpoint without providing any authentication credentials with the previous cURL command, the server responds with an HTTP/1.1 401 Unauthorized status code which is different from an HTTP 403 error message. An HTTP 401 code indicates that a client has not provided any valid authentication credentials for a request to a resource to be granted.

3.2.1 Test with cURL for Authentication and Authorization

To test for Authentication and Authorization using cURL with the authentication details from our in-memory user store, let’s consider a scenario where the user bill with a USER role tries to make a POST request to the http://localhost:8081/api/employee end-point in the application using the following cURL command.

curl -u bill:withers -X POST -i http://localhost:8081/api/employee -H 'Content-Type: application/json' -d '{"name":"Omozegie", "department": "Human Resources"}'

The server responds with a 403 Forbidden error because the user with username bill, does not have the required role of an ADMIN to perform the POST request as configured in the application. The output is shown below.

Fig 3.3 Output from entering correct credentials with wrong role and permission
Fig 3.3 Output from entering correct credentials with wrong role and permission

Now let’s use the credentials of the in-memory user with the appropriate authority/role we created to make an HTTP POST request to the http://localhost:8081/api/employee end-point. This will be the user with username thomas and password paine. The cURL command to perform the POST request is

curl -u thomas:paine -X POST -i http://localhost:8081/api/employee -H 'Content-Type: application/json' -d '{"name":"George", "department": "Marketing"}'

The server processes the request and responds with a HTTP 201 status code indicating that the POST request has been fulfilled with one resource created at the location /api/employee/5. This response confirms that the user’s authentication and authorization have been successful as shown in the image below.

Fig 3.4 Output from entering the correct credentials with right permissions
Fig 3.4 Output from entering the correct credentials with the right role and permission

4. Conclusion

In this article, we highlighted some scenarios that could lead to a 403 Forbidden error in a Spring Boot application. We explored how to use Spring Security to solve the HTTP 403 Forbidden error in Spring Boot applications. We discussed some of Spring Security features such as CSRF protection and configuration. We learned and configured authentication and authorization using Spring Security and discussed how authorization rules that define access control to resources can be handled in Spring Boot, solving the unauthorized 403 forbidden error.

5. Download the Source Code

This was an article on how to solve the HTTP 403 forbidden error in a Spring Boot application.

Download
You can download the full source code of this example here: Solving HTTP 403 Forbidden Error in Spring Boot Post Request

Omozegie Aziegbe

Omos holds a Master degree in Information Engineering with Network Management from the Robert Gordon University, Aberdeen. Omos is currently a freelance web/application developer who is currently focused on developing Java enterprise applications with the Jakarta EE framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button