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:
- 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
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
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.
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 Pattern | Description |
---|---|
.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. |
User.withUsername("thomas") | Defines a user with username thomas, password paine, having an ADMIN role. |
User | Defines a user with username bill, password withers, and having a USER role. |
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.
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.
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.
You can download the full source code of this example here: Solving HTTP 403 Forbidden Error in Spring Boot Post Request