Open In App

Spring REST – Response for Access Denied Request

Last Updated : 29 Dec, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

The Access Denied Request has an error code of 403. This is the error that is generated when the user tries to access certain resources that he is not authorized. The system displays a whitelabel error displaying the status code 403.

In this article, let us regenerate this error using spring security and then handle the same effectively using spring security.

Prerequisite Knowledge of role-based authorization in spring is required for the topic.

1. Recreate the Access Denied Error

Import a new project in the IDE of your choice and add the following dependencies to it:

  • Spring Web
  • Spring Security

The dependencies tag in your pom.xml file must contain these:

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


Next, create a controller class as follows:

Java




//Controller layer of application
  
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
  
@RestController
@RequestMapping("/api/v1")
public class MyController {
  
    /**
     * GET endpoint for public message.
     * Accessible by both USERS and ADMINS.
     *
     * @return Public message
     */
    @GetMapping("/public")
    public String publicMessage() {
        return "This is a PUBLIC endpoint. USERS and ADMINS can access it.";
    }
  
    /**
     * GET endpoint for private message.
     * Accessible only by ADMINS.
     *
     * @return Private message
     */
    @GetMapping("/secured")
    public String privateMessage() {
        return "This is a PRIVATE endpoint. Only ADMINS can access it.";
    }
}


In the above code sample, we intend to keep the public endpoints accessible to all users whereas the secured endpoints are visible to admins only.

Next, create a SecurityConfig.java class as follows:

Java




//Configuration Layer
  
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
  
@Configuration
@EnableWebSecurity
public class SecurityConfig {
  
    /**
     * Configures Spring Security's HTTP request authorization.
     *
     * @param http HttpSecurity object for customization
     * @return SecurityFilterChain Spring Security filterChain
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain
    securityFilterChain(HttpSecurity http) throws Exception
    {
        http
            // Authorize requests based on URL patterns and
            // user roles
            .authorizeHttpRequests(
                authorize
                -> authorize
                       .requestMatchers("/api/v1/secured")
                       .hasRole("ADMIN") // Only admins can access
                       .requestMatchers("/api/v1/public")
                       .hasAnyRole(
                           "ADMIN",
                           "USER") // Both admins and users can access
                       .anyRequest()
                       .authenticated()) // All other
                                         // requests require
                                         // authentication
  
            // Enable form login for authenticating users
            .formLogin(Customizer.withDefaults())
  
            // Enable Basic authentication for providing API access
            .httpBasic(Customizer.withDefaults());
  
        return http.build();
    }
  
    /**
     * Creates an in-memory user details service with
     * predefined users.
     *
     * @return UserDetailsService with test users
     */
    @Bean public UserDetailsService users()
    {
        User.UserBuilder users
            = User.withDefaultPasswordEncoder();
        UserDetails user = users.username("user")
                               .password("test123")
                               .roles("USER")
                               .build();
        UserDetails admin = users.username("admin")
                                .password("test123")
                                .roles("USER", "ADMIN")
                                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}


First, we configure spring security to allow only admins to access the secure endpoint. Next, we set up spring security to allow all users to access the public endpoint.

Next, we build users in memory as follows:

  • user 1 – username=user, password=test123, role= USER
  • user 2- username=admin, password=test123, role= USER, ADMIN

Now try running the code and fire up a private window in your computer. We will be testing 2 things:

  1. We will log in as `user` and we should be able to access the public endpoint only. We will also see the error when we try to visit the secured endpoint.
  2. We will log in as `admin` and we should be able to access all endpoints. No errors.

Let’s get started:

1. Login as a User

  • Before implementing the Access Denied Request, login as user

login page

  • Accessing the public endpoint after login as user

Public Endpoint

  • Accessing the private/secured endpoint after login as user

2. Login as ADMIN

  • Before implementing the Access Denied Request, login as Adminuser
logging in as admin with elevated privileges

logging in as admin with elevated privileges

  • Accessing the public endpoint after login as Admin
Admin can access Public endpoint

Admin can access Public endpoint

  • Accessing the private/secured endpoint after login as admin
Admins can access the secured endpoint

Admins can access the secured endpoint

Implementing Access Denied Error Response

Above Demonstration explains how the error occurs. Next, we want the user to know that he is trying to access a resource beyond his authority. To do this, we can edit the SecurityConfig class as follows:

Java




//Improved Security config with Access Denied Request
  
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
  
@Configuration
@EnableWebSecurity
public class SecurityConfig {
  
    /**
     * Configures Spring Security's HTTP request
     * authorization and exception handling.
     *
     * @param http HttpSecurity object for customization
     * @return SecurityFilterChain Spring Security filterChain
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain
    securityFilterChain(HttpSecurity http) throws Exception
    {
        http
            .authorizeHttpRequests(
                authorize
                -> authorize
                       // Restrict access based on URL
                       // patterns and roles
                       .requestMatchers("/api/v1/secured")
                       .hasRole("ADMIN") // Only admins
                       .requestMatchers("/api/v1/public")
                       .hasAnyRole(
                           "ADMIN",
                           "USER") // Both can access
                       .anyRequest()
                       .authenticated()) // All other
                                         // requests require
                                         // authentication
  
            // Configure exception handling for unauthorized access
            .exceptionHandling(
                exception
                -> exception.accessDeniedPage(
                    "/api/v1/error")) // Redirect
                                      // unauthorized users
                                      // to /api/v1/error
  
            // Enable form login and Basic authentication
            .formLogin(Customizer.withDefaults())
            .httpBasic(Customizer.withDefaults());
  
        return http.build();
    }
  
    /**
     * Creates an in-memory user details service with test
     * users.
     *
     * @return UserDetailsService with predefined users
     */
    @Bean public UserDetailsService users()
    {
        User.UserBuilder users
            = User.withDefaultPasswordEncoder();
        UserDetails user = users.username("user")
                               .password("test123")
                               .roles("USER")
                               .build();
        UserDetails admin = users.username("admin")
                                .password("test123")
                                .roles("USER", "ADMIN")
                                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}


Next, we create a custom controller to map “/api/v1/error”. Add the following controller to your controller class:-

Java




import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
  
@RestController
@RequestMapping("/api/v1")
public class MyController {
  
    /**
     * GET endpoint for displaying unauthorized access error message.
     *
     * @return Error message indicating unauthorized access
     */
    @GetMapping("/error")
    public String errorMessage() {
        return "You are not authorized to access this resource.";
    }
}


Let’s try running the code now. Remember, our goal is to be able to view a custom response indicating to the user that he isn’t allowed to access the secured URL. So let’s visit the /secured endpoint as `user` now.

  • Accessing the private/secured endpoint after login as user
  • Now it will not throw a whitelabel error displaying the status code 403, instead the custom error response is displayed.
Response for Access Denied Request

Response for Access Denied Request



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads