Implementing Attribute-Based Access Control (ABAC) in Java

Overview

Attribute-Based Access Control (ABAC) is a flexible authorization model that uses attributes (user, resource, action, environment) to make access control decisions. This implementation provides a comprehensive ABAC system in Java.

Core Concepts

ABAC Components

  • Subject: User or system requesting access
  • Resource: Object being accessed
  • Action: Operation being performed
  • Environment: Contextual information (time, location, etc.)
  • Policy: Rules defining access conditions

Implementation

1. Dependencies

<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>org.mvel</groupId>
<artifactId>mvel2</artifactId>
<version>2.4.14.Final</version>
</dependency>
</dependencies>

2. Core Domain Models

import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
// Subject (User) with attributes
public class Subject {
private String id;
private String username;
private String role;
private String department;
private String clearanceLevel;
private Map<String, Object> attributes;
public Subject(String id, String username) {
this.id = id;
this.username = username;
this.attributes = new HashMap<>();
}
// Getters and setters
public String getId() { return id; }
public String getUsername() { return username; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public String getClearanceLevel() { return clearanceLevel; }
public void setClearanceLevel(String clearanceLevel) { this.clearanceLevel = clearanceLevel; }
public Map<String, Object> getAttributes() { return attributes; }
public void setAttribute(String key, Object value) { attributes.put(key, value); }
public Object getAttribute(String key) { return attributes.get(key); }
}
// Resource with attributes
public class Resource {
private String id;
private String name;
private String type;
private String owner;
private String sensitivity;
private Map<String, Object> attributes;
public Resource(String id, String name, String type) {
this.id = id;
this.name = name;
this.type = type;
this.attributes = new HashMap<>();
}
// Getters and setters
public String getId() { return id; }
public String getName() { return name; }
public String getType() { return type; }
public String getOwner() { return owner; }
public void setOwner(String owner) { this.owner = owner; }
public String getSensitivity() { return sensitivity; }
public void setSensitivity(String sensitivity) { this.sensitivity = sensitivity; }
public Map<String, Object> getAttributes() { return attributes; }
public void setAttribute(String key, Object value) { attributes.put(key, value); }
public Object getAttribute(String key) { return attributes.get(key); }
}
// Action being performed
public class Action {
private String name;
private Map<String, Object> attributes;
public Action(String name) {
this.name = name;
this.attributes = new HashMap<>();
}
// Getters and setters
public String getName() { return name; }
public Map<String, Object> getAttributes() { return attributes; }
public void setAttribute(String key, Object value) { attributes.put(key, value); }
}
// Environment context
public class Environment {
private long timestamp;
private String ipAddress;
private String location;
private String deviceType;
private Map<String, Object> attributes;
public Environment() {
this.timestamp = System.currentTimeMillis();
this.attributes = new HashMap<>();
}
// Getters and setters
public long getTimestamp() { return timestamp; }
public String getIpAddress() { return ipAddress; }
public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
public String getDeviceType() { return deviceType; }
public void setDeviceType(String deviceType) { this.deviceType = deviceType; }
public Map<String, Object> getAttributes() { return attributes; }
public void setAttribute(String key, Object value) { attributes.put(key, value); }
}
// Access Request
public class AccessRequest {
private Subject subject;
private Resource resource;
private Action action;
private Environment environment;
public AccessRequest(Subject subject, Resource resource, Action action, Environment environment) {
this.subject = subject;
this.resource = resource;
this.action = action;
this.environment = environment;
}
// Getters
public Subject getSubject() { return subject; }
public Resource getResource() { return resource; }
public Action getAction() { return action; }
public Environment getEnvironment() { return environment; }
}

3. Policy Definition

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.ArrayList;
public class Policy {
public enum Effect {
PERMIT, DENY
}
private String id;
private String name;
private String description;
private Effect effect;
private List<String> targets;
private List<Rule> rules;
private int priority;
@JsonCreator
public Policy(@JsonProperty("id") String id,
@JsonProperty("name") String name,
@JsonProperty("description") String description,
@JsonProperty("effect") Effect effect,
@JsonProperty("targets") List<String> targets,
@JsonProperty("rules") List<Rule> rules,
@JsonProperty("priority") int priority) {
this.id = id;
this.name = name;
this.description = description;
this.effect = effect;
this.targets = targets != null ? targets : new ArrayList<>();
this.rules = rules != null ? rules : new ArrayList<>();
this.priority = priority;
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public String getDescription() { return description; }
public Effect getEffect() { return effect; }
public List<String> getTargets() { return targets; }
public List<Rule> getRules() { return rules; }
public int getPriority() { return priority; }
public boolean matchesTarget(AccessRequest request) {
if (targets.isEmpty()) return true;
for (String target : targets) {
if (evaluateTargetExpression(target, request)) {
return true;
}
}
return false;
}
private boolean evaluateTargetExpression(String expression, AccessRequest request) {
// Simple target matching - can be enhanced with expression evaluator
return expression.contains(request.getAction().getName()) ||
expression.contains(request.getResource().getType());
}
}
public class Rule {
private String id;
private String condition;
@JsonCreator
public Rule(@JsonProperty("id") String id,
@JsonProperty("condition") String condition) {
this.id = id;
this.condition = condition;
}
// Getters
public String getId() { return id; }
public String getCondition() { return condition; }
}

4. Policy Decision Point (PDP)

import org.mvel2.MVEL;
import java.io.Serializable;
import java.util.*;
public class PolicyDecisionPoint {
private PolicyAdministrationPoint pap;
private Map<String, Serializable> compiledExpressions;
public PolicyDecisionPoint(PolicyAdministrationPoint pap) {
this.pap = pap;
this.compiledExpressions = new HashMap<>();
}
public AuthorizationDecision decide(AccessRequest request) {
List<Policy> applicablePolicies = pap.findApplicablePolicies(request);
// Sort by priority (higher priority first)
applicablePolicies.sort((p1, p2) -> Integer.compare(p2.getPriority(), p1.getPriority()));
for (Policy policy : applicablePolicies) {
if (evaluatePolicy(policy, request)) {
return new AuthorizationDecision(policy.getEffect() == Policy.Effect.PERMIT, 
policy.getId(), 
"Policy " + policy.getName() + " applied");
}
}
// Default deny
return new AuthorizationDecision(false, null, "No applicable policy found");
}
private boolean evaluatePolicy(Policy policy, AccessRequest request) {
if (!policy.matchesTarget(request)) {
return false;
}
for (Rule rule : policy.getRules()) {
if (!evaluateRule(rule, request)) {
return false;
}
}
return true;
}
private boolean evaluateRule(Rule rule, AccessRequest request) {
try {
// Compile expression for better performance
String expressionKey = rule.getId() + "_" + rule.getCondition().hashCode();
Serializable compiledExpr = compiledExpressions.computeIfAbsent(expressionKey, 
k -> MVEL.compileExpression(rule.getCondition()));
// Create evaluation context
Map<String, Object> context = createEvaluationContext(request);
// Evaluate condition
Object result = MVEL.executeExpression(compiledExpr, context);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
System.err.println("Error evaluating rule " + rule.getId() + ": " + e.getMessage());
return false;
}
}
private Map<String, Object> createEvaluationContext(AccessRequest request) {
Map<String, Object> context = new HashMap<>();
// Add subject attributes
context.put("subject", request.getSubject());
context.put("user", request.getSubject()); // alias
context.put("userRole", request.getSubject().getRole());
context.put("userDepartment", request.getSubject().getDepartment());
context.put("userClearance", request.getSubject().getClearanceLevel());
// Add resource attributes
context.put("resource", request.getResource());
context.put("resourceType", request.getResource().getType());
context.put("resourceOwner", request.getResource().getOwner());
context.put("resourceSensitivity", request.getResource().getSensitivity());
// Add action
context.put("action", request.getAction());
context.put("actionName", request.getAction().getName());
// Add environment
context.put("environment", request.getEnvironment());
context.put("currentTime", request.getEnvironment().getTimestamp());
context.put("location", request.getEnvironment().getLocation());
context.put("ipAddress", request.getEnvironment().getIpAddress());
// Add custom attributes
context.putAll(request.getSubject().getAttributes());
context.putAll(request.getResource().getAttributes());
context.putAll(request.getAction().getAttributes());
context.putAll(request.getEnvironment().getAttributes());
return context;
}
}
public class AuthorizationDecision {
private final boolean permitted;
private final String policyId;
private final String reason;
private final long timestamp;
public AuthorizationDecision(boolean permitted, String policyId, String reason) {
this.permitted = permitted;
this.policyId = policyId;
this.reason = reason;
this.timestamp = System.currentTimeMillis();
}
// Getters
public boolean isPermitted() { return permitted; }
public String getPolicyId() { return policyId; }
public String getReason() { return reason; }
public long getTimestamp() { return timestamp; }
@Override
public String toString() {
return String.format("AuthorizationDecision{permitted=%s, policyId='%s', reason='%s'}",
permitted, policyId, reason);
}
}

5. Policy Administration Point (PAP)

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class PolicyAdministrationPoint {
private Map<String, Policy> policies;
private ObjectMapper objectMapper;
public PolicyAdministrationPoint() {
this.policies = new HashMap<>();
this.objectMapper = new ObjectMapper();
}
public void loadPoliciesFromFile(String filePath) throws IOException {
Policy[] policyArray = objectMapper.readValue(new File(filePath), Policy[].class);
for (Policy policy : policyArray) {
addPolicy(policy);
}
System.out.println("Loaded " + policyArray.length + " policies from " + filePath);
}
public void addPolicy(Policy policy) {
policies.put(policy.getId(), policy);
}
public void removePolicy(String policyId) {
policies.remove(policyId);
}
public Policy getPolicy(String policyId) {
return policies.get(policyId);
}
public List<Policy> getAllPolicies() {
return new ArrayList<>(policies.values());
}
public List<Policy> findApplicablePolicies(AccessRequest request) {
return policies.values().stream()
.filter(policy -> policy.matchesTarget(request))
.collect(Collectors.toList());
}
}

6. Policy Enforcement Point (PEP)

public class PolicyEnforcementPoint {
private PolicyDecisionPoint pdp;
public PolicyEnforcementPoint(PolicyDecisionPoint pdp) {
this.pdp = pdp;
}
public boolean checkAccess(Subject subject, Resource resource, Action action, Environment environment) {
AccessRequest request = new AccessRequest(subject, resource, action, environment);
AuthorizationDecision decision = pdp.decide(request);
// Log access decision
logAccessDecision(request, decision);
return decision.isPermitted();
}
public AuthorizationDecision checkAccessWithDetails(Subject subject, Resource resource, 
Action action, Environment environment) {
AccessRequest request = new AccessRequest(subject, resource, action, environment);
AuthorizationDecision decision = pdp.decide(request);
logAccessDecision(request, decision);
return decision;
}
private void logAccessDecision(AccessRequest request, AuthorizationDecision decision) {
System.out.println("Access Decision: " +
"User=" + request.getSubject().getUsername() +
", Resource=" + request.getResource().getName() +
", Action=" + request.getAction().getName() +
", Decision=" + (decision.isPermitted() ? "PERMIT" : "DENY") +
", Reason=" + decision.getReason());
}
}

7. Sample Policy Configuration

Create policies.json:

[
{
"id": "policy-001",
"name": "HR Department Document Access",
"description": "Allow HR department to access HR documents",
"effect": "PERMIT",
"targets": ["document", "read"],
"rules": [
{
"id": "rule-001",
"condition": "userDepartment == 'HR' && resourceType == 'document' && actionName == 'read'"
}
],
"priority": 10
},
{
"id": "policy-002",
"name": "Manager Edit Access",
"description": "Allow managers to edit documents in their department",
"effect": "PERMIT",
"targets": ["document", "edit"],
"rules": [
{
"id": "rule-002",
"condition": "userRole == 'MANAGER' && userDepartment == resourceOwner && actionName == 'edit'"
}
],
"priority": 20
},
{
"id": "policy-003",
"name": "Confidential Document Access",
"description": "Only allow access to confidential documents with proper clearance",
"effect": "PERMIT",
"targets": ["document"],
"rules": [
{
"id": "rule-003",
"condition": "resourceSensitivity == 'CONFIDENTIAL' && userClearance == 'HIGH'"
},
{
"id": "rule-004", 
"condition": "resourceSensitivity != 'CONFIDENTIAL'"
}
],
"priority": 30
},
{
"id": "policy-004",
"name": "Business Hours Restriction",
"description": "Only allow access during business hours",
"effect": "PERMIT",
"targets": [],
"rules": [
{
"id": "rule-005",
"condition": "isBusinessHours(currentTime)"
}
],
"priority": 5
},
{
"id": "policy-005", 
"name": "Deny Delete for Non-Admins",
"description": "Only admins can delete resources",
"effect": "DENY",
"targets": ["delete"],
"rules": [
{
"id": "rule-006",
"condition": "userRole != 'ADMIN' && actionName == 'delete'"
}
],
"priority": 100
}
]

8. Utility Functions

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class ABACUtils {
public static boolean isBusinessHours(long timestamp) {
ZonedDateTime dateTime = Instant.ofEpochMilli(timestamp)
.atZone(ZoneId.systemDefault());
int hour = dateTime.getHour();
int dayOfWeek = dateTime.getDayOfWeek().getValue();
// Business hours: Monday-Friday, 9 AM - 5 PM
return dayOfWeek >= 1 && dayOfWeek <= 5 && hour >= 9 && hour < 17;
}
public static Environment createDefaultEnvironment() {
Environment env = new Environment();
env.setIpAddress("192.168.1.100");
env.setLocation("Office");
env.setDeviceType("Desktop");
env.setAttribute("isBusinessHours", isBusinessHours(System.currentTimeMillis()));
return env;
}
}

9. Complete Example Usage

public class ABACExample {
public static void main(String[] args) {
try {
// Initialize ABAC components
PolicyAdministrationPoint pap = new PolicyAdministrationPoint();
PolicyDecisionPoint pdp = new PolicyDecisionPoint(pap);
PolicyEnforcementPoint pep = new PolicyEnforcementPoint(pdp);
// Load policies
pap.loadPoliciesFromFile("policies.json");
// Create test subjects
Subject hrUser = new Subject("user-001", "john.doe");
hrUser.setRole("EMPLOYEE");
hrUser.setDepartment("HR");
hrUser.setClearanceLevel("LOW");
Subject manager = new Subject("user-002", "jane.smith");
manager.setRole("MANAGER");
manager.setDepartment("Finance");
manager.setClearanceLevel("MEDIUM");
Subject admin = new Subject("user-003", "admin.user");
admin.setRole("ADMIN");
admin.setDepartment("IT");
admin.setClearanceLevel("HIGH");
// Create test resources
Resource hrDocument = new Resource("doc-001", "HR Policy", "document");
hrDocument.setOwner("HR");
hrDocument.setSensitivity("PUBLIC");
Resource confidentialDoc = new Resource("doc-002", "Financial Report", "document");
confidentialDoc.setOwner("Finance");
confidentialDoc.setSensitivity("CONFIDENTIAL");
Resource budgetDoc = new Resource("doc-003", "Budget Plan", "document");
budgetDoc.setOwner("Finance");
budgetDoc.setSensitivity("INTERNAL");
// Test scenarios
testScenario(pep, hrUser, hrDocument, new Action("read"), "HR reading HR document");
testScenario(pep, hrUser, confidentialDoc, new Action("read"), "HR reading confidential document");
testScenario(pep, manager, budgetDoc, new Action("edit"), "Manager editing budget");
testScenario(pep, manager, confidentialDoc, new Action("read"), "Manager reading confidential doc");
testScenario(pep, admin, confidentialDoc, new Action("delete"), "Admin deleting document");
testScenario(pep, manager, confidentialDoc, new Action("delete"), "Manager deleting document");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void testScenario(PolicyEnforcementPoint pep, Subject subject, 
Resource resource, Action action, String description) {
Environment env = ABACUtils.createDefaultEnvironment();
System.out.println("\n=== Scenario: " + description + " ===");
AuthorizationDecision decision = pep.checkAccessWithDetails(subject, resource, action, env);
System.out.println("User: " + subject.getUsername() + 
" (" + subject.getRole() + "/" + subject.getDepartment() + ")");
System.out.println("Resource: " + resource.getName() + 
" (" + resource.getSensitivity() + ")");
System.out.println("Action: " + action.getName());
System.out.println("Decision: " + (decision.isPermitted() ? "PERMIT" : "DENY"));
System.out.println("Reason: " + decision.getReason());
}
}

Advanced Features

1. Policy Combining Algorithms

public enum PolicyCombiningAlgorithm {
DENY_OVERRIDES,
PERMIT_OVERRIDES,
FIRST_APPLICABLE,
ONLY_ONE_APPLICABLE
}
public class AdvancedPDP {
private PolicyCombiningAlgorithm algorithm;
public AuthorizationDecision decideWithAlgorithm(AccessRequest request, 
List<Policy> policies,
PolicyCombiningAlgorithm algorithm) {
switch (algorithm) {
case DENY_OVERRIDES:
return denyOverrides(request, policies);
case PERMIT_OVERRIDES:
return permitOverrides(request, policies);
case FIRST_APPLICABLE:
return firstApplicable(request, policies);
default:
return firstApplicable(request, policies);
}
}
private AuthorizationDecision denyOverrides(AccessRequest request, List<Policy> policies) {
boolean atLeastOnePermit = false;
for (Policy policy : policies) {
if (evaluatePolicy(policy, request)) {
if (policy.getEffect() == Policy.Effect.DENY) {
return new AuthorizationDecision(false, policy.getId(), "Deny policy applied");
}
atLeastOnePermit = true;
}
}
return new AuthorizationDecision(atLeastOnePermit, null, 
atLeastOnePermit ? "Permit granted" : "No policy applied");
}
// Implement other algorithms similarly...
}

Security Considerations

  1. Policy Validation: Validate policies before loading
  2. Expression Security: Sanitize expression inputs to prevent code injection
  3. Audit Logging: Log all access decisions for compliance
  4. Performance: Cache compiled expressions and policy evaluations
  5. Policy Management: Secure policy administration interface

This ABAC implementation provides a flexible, attribute-based authorization system that can handle complex access control scenarios while maintaining performance and security.

Leave a Reply

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


Macro Nepal Helper