Open In App

Spring Security @PreAuthorize Annotation for Method Security

Last Updated : 17 Oct, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

In Spring Security, the @PreAuthorize annotation can apply method-level security by defining access control rules. It ensures that authorized users can only access specific functionalities before the method is invoked. This is especially useful in protecting sensitive operations such as CRUD methods, service methods, or business logic.

The @PreAuthorize annotation works by checking the provided security expression before executing the method. If the user doesn't meet the specified condition, an AccessDeniedException is thrown, preventing the method from running.

@PreAuthorize Annotation in Spring Security

The @PreAuthorize annotation is a key mechanism for applying method-level security in Spring Security. It enables you to specify security constraints on methods, ensuring only authorized users can execute them. This can be useful when restricting access based on roles, permissions, or other user-specific attributes.

What is @PreAuthorize?

The @PreAuthorize annotation is part of Spring Security's method-level security framework. It allows you to apply security rules directly on methods in service or controller classes. These rules are written using SpEL (Spring Expression Language), which helps you specify conditions based on user roles, authentication status, or other attributes.

When a method annotated with @PreAuthorize is called, Spring Security checks if the condition specified in the annotation is true for the current user. If the condition isn't met, an AccessDeniedException is thrown.

Example Signature of @PreAuthorize:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuthorize {
String value();
}

The value attribute takes a SpEL expression that defines the security condition to be met before the method is invoked.

SpEL (Spring Expression Language) in @PreAuthorize

SpEL is a powerful tool for evaluating expressions in Spring. When used with @PreAuthorize, it lets you create security rules based on:

  • Roles: Checking if the user has specific roles.
  • Authorities: Checking specific permissions granted to the user.
  • Authentication status: Checking if the user is authenticated or anonymous.
  • Custom conditions: Defining logic based on method parameters or user details.

How @PreAuthorize Works

When a method annotated with @PreAuthorize is invoked, Spring Security intercepts the call and evaluates the condition specified in the annotation. The flow is as follows:

  1. User Authentication: Spring Security checks if the user is authenticated.
  2. Expression Evaluation: The SpEL expression in @PreAuthorize is evaluated against the user’s authentication details (roles, authorities, etc.).
  3. Access Decision:
    • If the expression evaluates to true, the method is executed.
    • If the expression evaluates to false, an AccessDeniedException is thrown, preventing the method from executing.

Implementation of @PreAuthorize for Method Security

Let's implement the @PreAuthorize annotation in a Spring Boot application to secure a REST API method.

Step 1: Create a New Spring Boot Project

Create a new Spring Boot project in IntelliJ IDEA with the following options:

  • Name: spring-security-preauthorize
  • Language: Java
  • Type: Maven
  • Packaging: Jar

Click on the Next button.

Project Metadata

Step 2: Add the Dependencies

Add the following dependencies into the Spring Boot project.

  • Spring Web
  • Spring Security
  • Lombok
  • Spring Boot DevTools

Click on the Create button.

Add Dependencies

Project Structure

After the project creation done, the folder structure will look like the below image:

Project Folder Structure

Step 3: Configure Application Properties

Add the following to the application.properties file:

spring.application.name=spring-security-preauthorize

Step 4: Create the User Class

Java
package com.gfg.springsecuritypreauthorize;

// Class that represents a user with ID, username, and role fields
public class User {
    private Long id;
    private String username;
    private String role;

    // Constructor to initialize user details
    public User(Long id, String username, String role) {
        this.id = id;
        this.username = username;
        this.role = role;
    }

    // Getters to retrieve user details
    public Long getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

    public String getRole() {
        return role;
    }
}

This class defines the User object with fields such as id, username, and role. The constructor initializes these fields, and the getter methods provide access to them.

Step 5: Create the UserService Class

Java
package com.gfg.springsecuritypreauthorize;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    // Method that returns a mock user for demonstration
    public User getUser() {
        return new User(1L, "John Doe", "ROLE_USER");
    }
}

The UserService class contains a method that returns a mock user object. In a real-world application, this service would fetch the user details from a database or another data source.

Step 6: Create the SecurityConfig Class

Java
package com.gfg.springsecuritypreauthorize;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true) // Enable method-level security
public class SecurityConfig {

    // Configure the security filter chain
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable() // Disable CSRF for simplicity
                .authorizeRequests()
                .requestMatchers("/user").hasRole("USER") // Secure /user endpoint
                .anyRequest().authenticated() // Any other request must be authenticated
                .and()
                .httpBasic(); // Use Basic Authentication

        return http.build();
    }

    // Configure an in-memory authentication manager
    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder =
                http.getSharedObject(AuthenticationManagerBuilder.class);

        authenticationManagerBuilder
                .inMemoryAuthentication()
                .withUser("user").password(passwordEncoder().encode("password")).roles("USER");

        return authenticationManagerBuilder.build();
    }

    // Using BCrypt for password encoding
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

The SecurityConfig class configures Spring Security. It enables method-level security with @EnableMethodSecurity(prePostEnabled = true) and secures the /user endpoint so only users with the ROLE_USER can access it. An in-memory authentication manager is set up, and passwords are encoded using BCrypt.

Step 7: Create the UserController Class

Java
package com.gfg.springsecuritypreauthorize;

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

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    // this method is accessible only to users with the ROLE_USER
    @PreAuthorize("hasRole('ROLE_USER')")
    @GetMapping("/user")
    public User getUser() {
        return userService.getUser();
    }
}

The UserController class defines an API endpoint /user. The method getUser() is secured with the @PreAuthorize annotation, which ensures that only users with the role ROLE_USER can access this method.

Step 8: Main class

Java
package com.gfg.springsecuritypreauthorize;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringSecurityPreauthorizeApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityPreauthorizeApplication.class, args);
    }
}

pom.xml File:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gfg</groupId>
    <artifactId>spring-security-preauthorize</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-security-preauthorize</name>
    <description>spring-security-preauthorize</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <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-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Step 9: Run the Application

After completing the project, run the application and it will run on port 8080.

Application Runs

Step 10: Testing the Application

Now, we will test the endpoints using the postman tool.

GET http://localhost:8080/user

Response:

Postman UI

In this implementation, we demonstrated how to use the @PreAuthorize annotation to secure a method in the Spring Boot application based on the user roles.


Next Article

Similar Reads