Spring Method-Level Security. Secure RestController Methods

In this Spring Boot Security tutorial, you will learn how to use Spring method-level security to secure RestController methods with @PreAuthorize annotation.

If you are interested in video lessons, then I also show how to create user Roles and Authorities and how to use Spring Method Level Security annotations in my video course: RESTful Web Services, Spring Boot, Spring MVC, and JPA.

Add Spring Security Support

To secure your Spring Boot application with Spring Security you will need to add a Spring Security dependency to the pom.xml file. Open pom.xml file of your Spring Boot application and add the following dependency.

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Once you add the above dependency to pom.xml file and restart your Spring Boot application you will notice that all Web Service endpoints started to require user authentication. If you attempt to open a URL to one of the web service endpoints in the browser window, you will be prompted to provide user name and password.

Spring Security Login Page

To be able to log in you will need to provide the default Spring Security user name and password. However, to continue with this tutorial, you will need more than one default user. To learn how to set up in-memory users with roles and privileges please read this tutorial: Spring Security In-Memory Authentication.

Once you have configured users and their roles and authorities, you can continue with this tutorial and learn how to enable the method-level security and be able to secure specific methods of the RestController class.

Enable Method Level Security

To be able to apply Spring Security to specific methods in Rest Controller class of your Spring Boot application you will need to enable the method-level security in your Spring Boot application. To do that you will need to use the @EnableGlobalMethodSecurity annotation.

@EnableGlobalMethodSecurity Annotation

If you have enabled Spring Security in your Spring Boot application and have configured in-memory users with Roles and Authorities, then your Spring Boot application should have a Java class which extends the WebSecurityConfigurerAdapter.  To enable the method-level security in your Spring Boot application, annotate the class which extends the WebSecurityConfigurerAdapter with @EnableGlobalMethodSecurity(prePostEnabled = true) annotation. For example:

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurity extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception { 
        http
        .cors().and()
        .csrf().disable().authorizeRequests()
        .antMatchers("/users").hasRole("MANAGER")
        .anyRequest().authenticated()
        .and()
        .formLogin();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
 
        //User Role
        UserDetails theUser = User.withUsername("sergey")
                        .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode)
                        .password("12345678").roles("USER").build();
        
        //Manager Role 
        UserDetails theManager = User.withUsername("john")
                .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode)
                .password("87654321").roles("MANAGER").build();
        
  
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
              
        userDetailsManager.createUser(theUser);
        userDetailsManager.createUser(theManager);
        
        return userDetailsManager;

    }
}

Now when you have enabled the Global Method Security, you can apply Spring Security to specific methods in your Rest Controller classes.

Method-Level Security in Rest Controller Class

Let’s assume we have the following Rest Controller class.

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UsersController {

    @GetMapping("/users/status/check")
    public String usersStatusCheck() {
        return "Working for users";
    }

    @GetMapping("/managers/status/check")
    public String managersStatusCheck() {
        return "Working for managers";
    }
}

And let’s assume that we need to make HTTP GET requests to /users/status/check be allowed to authenticated users with a role “USER” and HTTP GET requests to /managers/status/check be allowed to users with a role “MANAGER”.

To restrict access to a /users/status/check to authenticated users with a role “USER” we will need to annotate the method which handles HTTP GET request to /users/status/check with a @PreAuthorize(“hasRole(‘USER’)”) annotation.

@PreAuthorize("hasRole('USER')")
@GetMapping("/users/status/check")
public String usersStatusCheck() {
    return "Working for users";
}

Let’s have a look at the updated Rest Controller class.

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UsersController {

    @PreAuthorize("hasRole('USER')")
    @GetMapping("/users/status/check")
    public String usersStatusCheck() {
        return "Working for users";
    }

    @PreAuthorize("hasRole('MANAGER')")
    @GetMapping("/managers/status/check")
    public String managersStatusCheck() {
        return "Working for managers";
    }
}

Authorities

If you have also configured Authorities for your users, then you can use @PreAuthorize annotation to restrict access to a certain method of your Rest Controller class to users with an authority mentioned in the @PreAuthorize annotationFor example:

@PreAuthorize("hasAuthority('DELETE_AUTHORITY')")

and here how it is used in the actual method in Rest Controller class which handles the HTTP DELETE Request.

@PreAuthorize("hasAuthority('DELETE_AUTHORITY')")
@DeleteMapping(path = "/{id}", produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE })
@ApiImplicitParams({
  @ApiImplicitParam(name = "authorization", value = "${userController.authorizationHeader.description}", paramType = "header") })
@Transactional
public OperationStatusModel deleteUser(@PathVariable String id) {
 OperationStatusModel returnValue = new OperationStatusModel();
 returnValue.setOperationName(RequestOperationName.DELETE.name());

 userService.deleteUser(id);

 returnValue.setOperationResult(RequestOperationStatus.SUCCESS.name());
 return returnValue;
}

@PreAuthorize at a Class Level

You can also use the @PreAuthorize annotation at a class level and annotate your entire class with @PreAuthorize. In this case, all methods in a class will be affected by the value used in this annotation. Method level @PreAuthorize annotation has a higher priority and will override the value used at the class level. Let’s have a look at the following code snippet.

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@PreAuthorize("hasRole('MANAGER')")
public class UsersController {

    @PreAuthorize("permitAll")
    @GetMapping("/users/status/check")
    public String usersStatusCheck() {
        return "Working for users";
    }

    @GetMapping("/managers/status/check")
    public String managersStatusCheck() {
        return "Working for managers";
    }
}

In the above code example, the @PreAuthorize annotation is used at a class level and all methods in the class are affected by it. Only users in role “MANAGER” will be able to access the /managers/status/check web service endpoint. However, the /users/status/check will be available to all users because the method-level @PreAuthorize annotation is overriding the class-level annotation.

I hope this tutorial was helpful to you.

If you would like to learn more about Spring Security, then have a look at the below list of online video courses. One of them might have the information you are looking for.

Leave a Reply

Your email address will not be published. Required fields are marked *