Skip to content

Security context-based validation pattern

The Security context-based validation pattern is a specific implementation of [[method-level security]] where access control logic is delegated to a dedicated validator bean that inspects the runtime security context.^[001-TODO__code-getway.md]

This pattern is commonly used in Spring Security applications to enforce permissions that depend on dynamic parameters, such as the value of a method argument.^[001-TODO__code-getway.md]

Implementation

The pattern typically consists of three components: a validator bean, a secured controller method, and the parameterized permission logic.

Validator Bean

A standard Spring component (@Component or defined via @Bean) is created to encapsulate the security logic^[001-TODO__code-getway.md]. This bean accesses the current user's authentication details via SecurityContextHolder.getContext().getAuthentication()^[001-TODO__code-getway.md].

The logic extracts the principal (often cast to a custom UserDetails implementation) and compares the user's granted authorities against a required string derived from the input parameters^[001-TODO__code-getway.md].

Controller Integration

The controller is secured using the @PreAuthorize annotation^[001-TODO__code-getway.md]. Instead of checking a hard-coded role (e.g., hasRole('ADMIN')), the annotation references the validator bean using Spring Expression Language (SpEL), typically with a syntax like @beanName.hasAuthority(#paramName)^[001-TODO__code-getway.md].

This passes the method argument (e.g., reportType) directly to the validator, allowing the security check to vary based on the specific data being accessed^[001-TODO__code-getway.md].

Example Code

In the following example, ReportSecurityValidator checks if the user has a specific authority string associated with the requested ReportType^[001-TODO__code-getway.md].

// Validator Bean
public class ReportSecurityValidator {
    public boolean hasAuthority(@NonNull ReportType reportType) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        AccountDetail details = (AccountDetail) auth.getPrincipal();
        String requiredAuthority = reportType.getAuthority();

        return details.getAuthorities().stream()
                .anyMatch(a -> a.getAuthority().equals(requiredAuthority));
    }
}

// Controller
@RestController
public class ReportManageController {

    @PreAuthorize("@reportSecurityValidator.hasAuthority(#reportType)")
    @GetMapping("/v1/report")
    public SuccessResp<Void> startingMakeReport(@RequestParam ReportType reportType) {
        // ...
    }
}

Benefits

  • Dynamic Authorization: Permissions are evaluated at runtime based on method arguments, enabling fine-grained access control for resources that share the same endpoint but represent different data types or domains^[001-TODO__code-getway.md].
  • Centralized Logic: Security rules are managed within a dedicated bean, promoting cleaner code and easier maintenance compared to duplicating checks inside controller methods^[001-TODO__code-getway.md].
  • Type Safety: By using domain objects (like Enums) as parameters, the validation logic can map specific types to specific permission strings (e.g., PROXY -> "proxy:domain:edit")^[001-TODO__code-getway.md].

Sources

  • 001-TODO__code-getway.md