Skip to content
Ashish.
All posts
Diagram illustrating ABAC implementation in Spring Security using AuthorizationManager.
6 min readBackendJava DevelopersFeatured#spring security#abac#java#authorization#security#backend#pdp

Spring Security ABAC: Implementing AuthorizationManager

A technical guide on implementing Attribute-Based Access Control in Spring Security using AuthorizationManager, MethodSecurity, and external PDPs.

By Ashish KumarPart 4 of Attribute-Based Access Control

Spring Security ABAC

Attribute-Based Access Control (ABAC) moves beyond static roles to evaluate dynamic attributes—user properties, resource metadata, and environmental context—at the moment of access. In Spring Security 6, the framework deprecates the legacy AccessDecisionManager and AccessDecisionVoter in favor of AuthorizationManager, though @PreAuthorize still functions via adapters. This shift provides a type-safe, fluent mechanism for implementing ABAC, whether you compute policies in-memory or delegate to an external Policy Decision Point (PDP).

This article is Part 4 of the Attribute-Based Access Control series.

The Mechanism of AuthorizationManager

The core of ABAC in Spring Security is AuthorizationManager<T>. Unlike role-based checks that only inspect the Authentication object, ABAC requires access to the resource being protected. AuthorizationManager accepts two arguments in its check method:

AuthorizationDecision check(Supplier<Authentication> authentication, T object);

Here, authentication holds the principal and their attributes, while object is the secured resource (e.g., a Document entity, a HttpServletRequest, or a domain object). This design allows the implementation to inspect attributes of both the subject and the target, which is the fundamental requirement for ABAC.

When check returns an AuthorizationDecision with isAllowed() == false, access is denied. Returning normally with isAllowed() == true grants access. This simple contract replaces the complex hierarchy of previous versions, allowing developers to write plain Java functions or lambdas for policy evaluation.

Technical diagram showing the AuthorizationManager interface in Spring Security. The diagram illustrates the check method accepting two inputs : a Supplier<Authentication> representing the user's identity and attributes, and a generic Object T representing the secured resource…

In-JVM ABAC Implementation

For many applications, ABAC logic is simple enough to run within the JVM. Consider a scenario where a User can only access a Document if they are the owner. We define a custom AuthorizationManager<Document>. This approach contrasts with the deprecated AccessDecisionManager that it replaces, offering a more flexible and type-safe contract for attribute evaluation.

public class DocumentOwnershipManager implements AuthorizationManager<Document> {
 
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Document document) {
        String username = authentication.get().getName();
        if (!username.equals(document.getOwnerId())) {
            return new AuthorizationDecision(false);
        }
        return new AuthorizationDecision(true);
    }
}

To apply this to HTTP requests, we map the request URI to the Document object and register the manager. Spring Security’s HttpSecurity DSL allows us to configure request-level authorization:

http.securityMatcher("/api/documents/**")
    .authorizeHttpRequests(authz -> authz
        .requestMatchers(HttpMethod.GET, "/api/documents/**")
        .access(new DocumentOwnershipManager())
    );

For method-level security, we use the same manager but register it via MethodSecurityConfiguration. This ensures that any method annotated with @PreAuthorize or configured via @EnableMethodSecurity can leverage the same ABAC logic.

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
 
    @Bean
    public AuthorizationManager<MethodInvocation> methodAuthorizationManager() {
        return (authentication, methodInvocation) -> {
            // Extract document ID from method arguments
            Document doc = extractDocument(methodInvocation);
            // Reuse the same logic as HTTP check
            // ... implementation details ...
        };
    }
}

This approach keeps policy logic close to the codebase, making it easy to test and debug. However, it scales poorly when policies become complex or require frequent updates without redeployment.

Delegating to an External PDP

When policies are complex, dynamic, or shared across multiple services, delegating to an external Policy Decision Point (PDP) like Open Policy Agent (OPA) becomes necessary. The AuthorizationManager acts as a Policy Enforcement Point (PEP), forwarding the request context to the PDP.

In this architecture, the AuthorizationManager extracts ABAC attributes from the Authentication and the secured object, then sends them to the PDP via HTTP.

public class OpaAuthorizationManager implements AuthorizationManager<Document> {
 
    private final RestTemplate restTemplate;
    private final String opaUrl;
 
    public OpaAuthorizationManager(RestTemplate restTemplate, String opaUrl) {
        this.restTemplate = restTemplate;
        this.opaUrl = opaUrl;
    }
 
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Document document) {
        Map<String, Object> input = new HashMap<>();
        input.put("user", authentication.get().getPrincipal());
        input.put("resource", document);
 
        ResponseEntity<Boolean> response = restTemplate.postForEntity(
            opaUrl + "/v1/data/myapp/allow", input, Boolean.class
        );
 
        if (!Boolean.TRUE.equals(response.getBody())) {
            return new AuthorizationDecision(false);
        }
        return new AuthorizationDecision(true);
    }
}

This pattern introduces latency due to network calls. To mitigate this, you should implement caching strategies for policy results or use async evaluation where possible. Additionally, ensure that the PDP is highly available, as it becomes a single point of failure for authorization decisions.

Method-Level ABAC with SpEL

Spring Security integrates AuthorizationManager with Spring Expression Language (SpEL) via @PreAuthorize. While you can use full managers, SpEL offers a concise way to define ABAC rules inline.

For example, to allow access only if the user’s department matches the document’s department:

@PreAuthorize("@docManager.check(authentication, #document)")
public Document getDocument(@RequestBody Document document) {
    return repository.findById(document.getId()).orElseThrow();
}

Here, @docManager refers to a Spring bean wrapping the same DocumentOwnershipManager logic shown earlier. This hybrid approach allows you to use SpEL for simple attribute checks and full AuthorizationManager implementations for complex logic involving multiple attributes or external calls.

Conclusion

Spring Security’s AuthorizationManager provides a solid foundation for ABAC. By leveraging the check method’s access to both authentication and resource objects, developers can implement fine-grained controls that adapt to dynamic contexts. Whether evaluating policies in-memory or delegating to an external PDP, the consistent API ensures that ABAC is both flexible and maintainable. For detailed contract specifications, refer to the Spring Security Reference Documentation.

Related posts