Spring Method-Level Security with @PreAuthorize

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.

To learn more about method-level security annotations read:

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 username 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 apply Spring Security to specific methods in the 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 @EnableMethodSecurity annotation.

@EnableMethodSecurity Annotation

@EnableMethodSecurity is a Spring annotation used to enable method-level security in a Spring application.

When you use this annotation, Spring will create a proxy for the class containing the secured methods and will intercept calls to those methods to check if the caller has the required permissions to execute them.

This annotation works together with other annotations, such as @PreAuthorize, @PostAuthorize, @Secured, and @RolesAllowed, which are used to specify the access control rules for the methods.

For example, you can use @PreAuthorize to specify that only users with a certain role or authority can call a particular method, or you can use @PostAuthorize to specify that the method should only return data that the caller is authorized to see.

An example with @EnableMethodSecurity annotation

Suppose you have enabled Spring Security in your Spring Boot application and have configured in-memory users with Roles and Authorities, in that case, your Spring Boot application should have a Java class annotated with @EnableWebSecuirty annotation To enable the method-level security in your Spring Boot application, annotate the class with the following annotations:

  •  @EnableMethodSecurity(prePostEnabled = true), and
  • @Configuration annotation.
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;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class WebSecurity {
    
    @Bean
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {
     http
    .cors(withDefaults())
    .csrf((csrf) -> csrf.disable())
     .authorizeHttpRequests((authz) -> authz.requestMatchers("/users").hasRole("MANAGER")
                .anyRequest().authenticated())
    .formLogin((form) -> form.loginPage("/login"));

      return http.build();
    }

 
    @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

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";
    }
}

@PreAuthorize annotation

@PreAuthorize is a Spring Security annotation used to specify an expression that should be evaluated before a method is invoked to determine whether the caller is authorized to execute it.

The expression specified in @PreAuthorize can use the Spring Expression Language (SpEL) to reference the method arguments, the security context, and other variables. The result of the expression must be a boolean value, where true indicates that the caller is authorized to proceed with the method execution, and false indicates that the caller is not authorized.

Let’s assume that we need to make HTTP GET requests to /users/status/check be allowed to authenticate 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

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

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

and here is how it is used in the actual method in the 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 the 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 overrides the class-level annotation.

I hope this tutorial was helpful to you.

If you want to learn more about Spring Security, 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 *