Introduction to XACML
XACML (eXtensible Access Control Markup Language) is an OASIS standard for attribute-based access control (ABAC). It provides a fine-grained, flexible authorization framework that evaluates access requests based on attributes of the subject, resource, action, and environment.
Key XACML Concepts
Core Components
- Policy Decision Point (PDP) - Evaluates policies and renders decisions
- Policy Administration Point (PAP) - Manages policy storage and creation
- Policy Information Point (PIP) - Retrieves attribute values
- Policy Enforcement Point (PEP) - Intercepts requests and enforces decisions
Decision Types
- Permit - Access granted
- Deny - Access denied
- NotApplicable - No policy applies
- Indeterminate - Evaluation error occurred
Dependencies and Setup
Maven Configuration
<properties> <balana.version>1.1.0</balana.version> </properties> <dependencies> <!-- Balana - WSO2 XACML Implementation --> <dependency> <groupId>org.wso2.balana</groupId> <artifactId>balana</artifactId> <version>${balana.version}</version> </dependency> <!-- Alternative: AuthzForce --> <dependency> <groupId>org.ow2.authzforce</groupId> <artifactId>authzforce-ce-core-pdp-engine</artifactId> <version>16.0.0</version> </dependency> <!-- XML Processing --> <dependency> <groupId>org.apache.santuario</groupId> <artifactId>xmlsec</artifactId> <version>2.3.0</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> </dependencies> Core XACML Engine Implementation
Basic PDP Engine
package com.xacml.engine; import org.wso2.balana.*; import org.wso2.balana.finder.*; import org.wso2.balana.finder.impl.*; import org.wso2.balana.PDP; import org.wso2.balana.PDPConfig; import org.wso2.balana.ctx.*; import org.wso2.balana.ctx.xacml3.RequestCtx; import org.wso2.balana.ctx.xacml3.ResponseCtx; import org.wso2.balana.xacml3.*; import java.io.ByteArrayInputStream; import java.io.StringReader; import java.util.*; public class BasicPDPEngine { private PDP pdp; private PolicyFinder policyFinder; private PolicyFinderModule policyFinderModule; public BasicPDPEngine() { initializePDP(); } private void initializePDP() { try { // Create policy finder policyFinder = new PolicyFinder(); policyFinderModule = new FileBasedPolicyFinderModule(); Set<PolicyFinderModule> finderModules = new HashSet<>(); finderModules.add(policyFinderModule); policyFinder.setModules(finderModules); // Create attribute finder AttributeFinder attributeFinder = new AttributeFinder(); Set<AttributeFinderModule> attributeFinderModules = new HashSet<>(); attributeFinderModules.add(new CurrentEnvModule()); attributeFinder.setModules(attributeFinderModules); // Create resource finder ResourceFinder resourceFinder = new ResourceFinder(); Set<ResourceFinderModule> resourceFinderModules = new HashSet<>(); resourceFinder.setModules(resourceFinderModules); // Create PDP configuration PDPConfig pdpConfig = new PDPConfig(attributeFinder, policyFinder, resourceFinder); // Create PDP pdp = new PDP(pdpConfig); } catch (Exception e) { throw new RuntimeException("Failed to initialize PDP engine", e); } } /** * Evaluate XACML request */ public ResponseCtx evaluateRequest(String xacmlRequest) throws Exception { RequestCtx request = RequestCtx.getInstance(new ByteArrayInputStream(xacmlRequest.getBytes())); return pdp.evaluate(request); } /** * Evaluate access request with simplified parameters */ public Decision evaluateAccess(String subjectId, String subjectRole, String resourceId, String action, Map<String, String> environment) throws Exception { String xacmlRequest = createXACMLRequest(subjectId, subjectRole, resourceId, action, environment); ResponseCtx response = evaluateRequest(xacmlRequest); return extractDecision(response); } private String createXACMLRequest(String subjectId, String subjectRole, String resourceId, String action, Map<String, String> environment) { StringBuilder request = new StringBuilder(); request.append("<Request xmlns=\"urn:oasis:names:tc:xacml:3.0:core:schema:wd-17\" ") .append("CombinedDecision=\"false\" ReturnPolicyIdList=\"false\">\n") .append(" <Attributes Category=\"urn:oasis:names:tc:xacml:1.0:subject-category:access-subject\">\n") .append(" <Attribute AttributeId=\"urn:oasis:names:tc:xacml:1.0:subject:subject-id\" IncludeInResult=\"false\">\n") .append(" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">").append(subjectId).append("</AttributeValue>\n") .append(" </Attribute>\n") .append(" <Attribute AttributeId=\"urn:oasis:names:tc:xacml:2.0:subject:role\" IncludeInResult=\"false\">\n") .append(" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">").append(subjectRole).append("</AttributeValue>\n") .append(" </Attribute>\n") .append(" </Attributes>\n") .append(" <Attributes Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:resource\">\n") .append(" <Attribute AttributeId=\"urn:oasis:names:tc:xacml:1.0:resource:resource-id\" IncludeInResult=\"false\">\n") .append(" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">").append(resourceId).append("</AttributeValue>\n") .append(" </Attribute>\n") .append(" </Attributes>\n") .append(" <Attributes Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:action\">\n") .append(" <Attribute AttributeId=\"urn:oasis:names:tc:xacml:1.0:action:action-id\" IncludeInResult=\"false\">\n") .append(" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">").append(action).append("</AttributeValue>\n") .append(" </Attribute>\n") .append(" </Attributes>\n"); if (environment != null && !environment.isEmpty()) { request.append(" <Attributes Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:environment\">\n"); for (Map.Entry<String, String> entry : environment.entrySet()) { request.append(" <Attribute AttributeId=\"").append(entry.getKey()).append("\" IncludeInResult=\"false\">\n") .append(" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">").append(entry.getValue()).append("</AttributeValue>\n") .append(" </Attribute>\n"); } request.append(" </Attributes>\n"); } request.append("</Request>"); return request.toString(); } private Decision extractDecision(ResponseCtx response) { if (response.getResults().length == 0) { return Decision.INDETERMINATE; } Result result = response.getResults()[0]; return result.getDecision(); } /** * Load policy from string */ public void loadPolicy(String policyXml) throws Exception { Policy policy = Policy.getInstance(new StringReader(policyXml)); ((FileBasedPolicyFinderModule) policyFinderModule).loadPolicy(policy); } /** * Load policy from file */ public void loadPolicyFromFile(String filePath) throws Exception { ((FileBasedPolicyFinderModule) policyFinderModule).loadPolicy(filePath); } } Policy Management
Policy Administration Point (PAP)
package com.xacml.pap; import org.wso2.balana.Policy; import org.wso2.balana.PolicySet; import org.wso2.balana.combine.PolicyCombiningAlgorithm; import org.wso2.balana.combine.xacml3.DenyOverridesPolicyAlg; import java.io.StringReader; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class PolicyAdministrationPoint { private Map<String, Policy> policies = new ConcurrentHashMap<>(); private Map<String, PolicySet> policySets = new ConcurrentHashMap<>(); private PolicyCombiningAlgorithm combiningAlgorithm = new DenyOverridesPolicyAlg(); /** * Add a policy to the repository */ public void addPolicy(String policyId, String policyXml) throws Exception { Policy policy = Policy.getInstance(new StringReader(policyXml)); policies.put(policyId, policy); } /** * Remove a policy from the repository */ public void removePolicy(String policyId) { policies.remove(policyId); } /** * Create a policy set from multiple policies */ public void createPolicySet(String policySetId, List<String> policyIds, String combiningAlgorithmId) throws Exception { List<Policy> policyList = new ArrayList<>(); for (String policyId : policyIds) { Policy policy = policies.get(policyId); if (policy != null) { policyList.add(policy); } } PolicySet policySet = new PolicySet(policySetId, combiningAlgorithm, null, policyList, null, null, null); policySets.put(policySetId, policySet); } /** * Get policy by ID */ public Policy getPolicy(String policyId) { return policies.get(policyId); } /** * Get all policies */ public Collection<Policy> getAllPolicies() { return policies.values(); } /** * Validate policy syntax */ public boolean validatePolicy(String policyXml) { try { Policy.getInstance(new StringReader(policyXml)); return true; } catch (Exception e) { return false; } } /** * Export policies as XML */ public String exportPolicies() { StringBuilder sb = new StringBuilder(); sb.append("<PolicyRepository>\n"); for (Policy policy : policies.values()) { sb.append(policy.encode()).append("\n"); } sb.append("</PolicyRepository>"); return sb.toString(); } } Policy Information Point (PIP)
package com.xacml.pip; import org.wso2.balana.attr.*; import org.wso2.balana.ctx.EvaluationCtx; import org.wso2.balana.finder.AttributeFinderModule; import java.net.URI; import java.util.*; public class CustomAttributeFinder extends AttributeFinderModule { private Map<String, Map<String, String>> userAttributes = new HashMap<>(); private Map<String, Map<String, String>> resourceAttributes = new HashMap<>(); public CustomAttributeFinder() { initializeSampleData(); } private void initializeSampleData() { // Sample user attributes Map<String, String> user1Attrs = new HashMap<>(); user1Attrs.put("department", "HR"); user1Attrs.put("clearance", "high"); user1Attrs.put("location", "US"); userAttributes.put("user1", user1Attrs); Map<String, String> user2Attrs = new HashMap<>(); user2Attrs.put("department", "Engineering"); user2Attrs.put("clearance", "medium"); user2Attrs.put("location", "EU"); userAttributes.put("user2", user2Attrs); // Sample resource attributes Map<String, String> resource1Attrs = new HashMap<>(); resource1Attrs.put("sensitivity", "confidential"); resource1Attrs.put("owner", "HR"); resourceAttributes.put("document1", resource1Attrs); } @Override public Set<String> getSupportedCategories() { Set<String> categories = new HashSet<>(); categories.add("urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"); categories.add("urn:oasis:names:tc:xacml:3.0:attribute-category:resource"); categories.add("urn:oasis:names:tc:xacml:3.0:attribute-category:environment"); return categories; } @Override public Set<String> getSupportedIds() { Set<String> ids = new HashSet<>(); ids.add("urn:oasis:names:tc:xacml:1.0:subject:subject-id"); ids.add("urn:oasis:names:tc:xacml:2.0:subject:role"); ids.add("urn:oasis:names:tc:xacml:1.0:subject:department"); ids.add("urn:oasis:names:tc:xacml:1.0:subject:clearance"); ids.add("urn:oasis:names:tc:xacml:1.0:resource:resource-id"); ids.add("urn:oasis:names:tc:xacml:1.0:resource:sensitivity"); ids.add("urn:oasis:names:tc:xacml:1.0:resource:owner"); ids.add("urn:oasis:names:tc:xacml:1.0:environment:current-time"); ids.add("urn:oasis:names:tc:xacml:1.0:environment:current-date"); return ids; } @Override public AttributeValue findAttribute(URI category, URI attributeType, URI attributeId, String issuer, EvaluationCtx context) { String categoryStr = category.toString(); String attributeIdStr = attributeId.toString(); try { if (categoryStr.equals("urn:oasis:names:tc:xacml:1.0:subject-category:access-subject")) { return findSubjectAttribute(attributeIdStr, context); } else if (categoryStr.equals("urn:oasis:names:tc:xacml:3.0:attribute-category:resource")) { return findResourceAttribute(attributeIdStr, context); } else if (categoryStr.equals("urn:oasis:names:tc:xacml:3.0:attribute-category:environment")) { return findEnvironmentAttribute(attributeIdStr, context); } } catch (Exception e) { // Log error and return null } return null; } private AttributeValue findSubjectAttribute(String attributeId, EvaluationCtx context) { // Extract subject ID from context String subjectId = extractSubjectId(context); if (subjectId == null) return null; Map<String, String> attrs = userAttributes.get(subjectId); if (attrs == null) return null; String value = attrs.get(getSimpleAttributeName(attributeId)); if (value != null) { return StringAttribute.getInstance(value); } return null; } private AttributeValue findResourceAttribute(String attributeId, EvaluationCtx context) { // Extract resource ID from context String resourceId = extractResourceId(context); if (resourceId == null) return null; Map<String, String> attrs = resourceAttributes.get(resourceId); if (attrs == null) return null; String value = attrs.get(getSimpleAttributeName(attributeId)); if (value != null) { return StringAttribute.getInstance(value); } return null; } private AttributeValue findEnvironmentAttribute(String attributeId, EvaluationCtx context) { switch (attributeId) { case "urn:oasis:names:tc:xacml:1.0:environment:current-time": return StringAttribute.getInstance(new Date().toString()); case "urn:oasis:names:tc:xacml:1.0:environment:current-date": return StringAttribute.getInstance(java.time.LocalDate.now().toString()); default: return null; } } private String extractSubjectId(EvaluationCtx context) { // Implementation to extract subject ID from evaluation context // This is a simplified version return "user1"; // Placeholder } private String extractResourceId(EvaluationCtx context) { // Implementation to extract resource ID from evaluation context // This is a simplified version return "document1"; // Placeholder } private String getSimpleAttributeName(String fullAttributeId) { // Convert full attribute ID to simple name if (fullAttributeId.contains(":")) { return fullAttributeId.substring(fullAttributeId.lastIndexOf(":") + 1); } return fullAttributeId; } // Methods to manage attribute data public void addUserAttribute(String userId, String attributeName, String attributeValue) { userAttributes.computeIfAbsent(userId, k -> new HashMap<>()) .put(attributeName, attributeValue); } public void addResourceAttribute(String resourceId, String attributeName, String attributeValue) { resourceAttributes.computeIfAbsent(resourceId, k -> new HashMap<>()) .put(attributeName, attributeValue); } } Sample Policies
Role-Based Access Policy
public class SamplePolicies { public static final String ROLE_BASED_POLICY = "<Policy xmlns=\"urn:oasis:names:tc:xacml:3.0:core:schema:wd-17\" " + "PolicyId=\"role-based-policy\" RuleCombiningAlgId=\"urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides\">\n" + " <Target>\n" + " <AnyOf>\n" + " <AllOf>\n" + " <Match MatchId=\"urn:oasis:names:tc:xacml:1.0:function:string-equal\">\n" + " <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">document</AttributeValue>\n" + " <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:1.0:resource:resource-type\" " + "Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:resource\" DataType=\"http://www.w3.org/2001/XMLSchema#string\" MustBePresent=\"true\"/>\n" + " </Match>\n" + " </AllOf>\n" + " </AnyOf>\n" + " </Target>\n" + " <Rule Effect=\"Permit\" RuleId=\"admin-rule\">\n" + " <Target>\n" + " <AnyOf>\n" + " <AllOf>\n" + " <Match MatchId=\"urn:oasis:names:tc:xacml:1.0:function:string-equal\">\n" + " <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">admin</AttributeValue>\n" + " <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:2.0:subject:role\" " + "Category=\"urn:oasis:names:tc:xacml:1.0:subject-category:access-subject\" DataType=\"http://www.w3.org/2001/XMLSchema#string\" MustBePresent=\"true\"/>\n" + " </Match>\n" + " </AllOf>\n" + " </AnyOf>\n" + " </Target>\n" + " </Rule>\n" + " <Rule Effect=\"Permit\" RuleId=\"user-rule\">\n" + " <Target>\n" + " <AnyOf>\n" + " <AllOf>\n" + " <Match MatchId=\"urn:oasis:names:tc:xacml:1.0:function:string-equal\">\n" + " <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">user</AttributeValue>\n" + " <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:2.0:subject:role\" " + "Category=\"urn:oasis:names:tc:xacml:1.0:subject-category:access-subject\" DataType=\"http://www.w3.org/2001/XMLSchema#string\" MustBePresent=\"true\"/>\n" + " </Match>\n" + " </AllOf>\n" + " </AnyOf>\n" + " </Target>\n" + " <Condition>\n" + " <Apply FunctionId=\"urn:oasis:names:tc:xacml:1.0:function:string-equal\">\n" + " <Apply FunctionId=\"urn:oasis:names:tc:xacml:1.0:function:string-one-and-only\">\n" + " <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:1.0:action:action-id\" " + "Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:action\" DataType=\"http://www.w3.org/2001/XMLSchema#string\" MustBePresent=\"true\"/>\n" + " </Apply>\n" + " <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">read</AttributeValue>\n" + " </Apply>\n" + " </Condition>\n" + " </Rule>\n" + " <Rule Effect=\"Deny\" RuleId=\"deny-rule\"/>\n" + "</Policy>"; public static final String TIME_BASED_POLICY = "<Policy xmlns=\"urn:oasis:names:tc:xacml:3.0:core:schema:wd-17\" " + "PolicyId=\"time-based-policy\" RuleCombiningAlgId=\"urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides\">\n" + " <Rule Effect=\"Permit\" RuleId=\"business-hours-rule\">\n" + " <Condition>\n" + " <Apply FunctionId=\"urn:oasis:names:tc:xacml:1.0:function:and\">\n" + " <Apply FunctionId=\"urn:oasis:names:tc:xacml:2.0:function:time-greater-than-or-equal\">\n" + " <Apply FunctionId=\"urn:oasis:names:tc:xacml:1.0:function:time-one-and-only\">\n" + " <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:1.0:environment:current-time\" " + "Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:environment\" DataType=\"http://www.w3.org/2001/XMLSchema#time\" MustBePresent=\"true\"/>\n" + " </Apply>\n" + " <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#time\">09:00:00</AttributeValue>\n" + " </Apply>\n" + " <Apply FunctionId=\"urn:oasis:names:tc:xacml:2.0:function:time-less-than-or-equal\">\n" + " <Apply FunctionId=\"urn:oasis:names:tc:xacml:1.0:function:time-one-and-only\">\n" + " <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:1.0:environment:current-time\" " + "Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:environment\" DataType=\"http://www.w3.org/2001/XMLSchema#time\" MustBePresent=\"true\"/>\n" + " </Apply>\n" + " <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#time\">17:00:00</AttributeValue>\n" + " </Apply>\n" + " </Apply>\n" + " </Condition>\n" + " </Rule>\n" + " <Rule Effect=\"Deny\" RuleId=\"deny-after-hours\"/>\n" + "</Policy>"; } Spring Boot Integration
Configuration Class
package com.xacml.config; import com.xacml.engine.BasicPDPEngine; import com.xacml.pap.PolicyAdministrationPoint; import com.xacml.pip.CustomAttributeFinder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class XACMLConfig { @Bean public BasicPDPEngine pdpEngine() { return new BasicPDPEngine(); } @Bean public PolicyAdministrationPoint policyAdministrationPoint() { return new PolicyAdministrationPoint(); } @Bean public CustomAttributeFinder attributeFinder() { return new CustomAttributeFinder(); } } REST Controller
package com.xacml.controller; import com.xacml.engine.BasicPDPEngine; import com.xacml.pap.PolicyAdministrationPoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.wso2.balana.ctx.xacml3.ResponseCtx; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/xacml") public class XACMLController { @Autowired private BasicPDPEngine pdpEngine; @Autowired private PolicyAdministrationPoint pap; @PostMapping("/evaluate") public ResponseEntity<Map<String, Object>> evaluateRequest(@RequestBody AccessRequest request) { Map<String, Object> response = new HashMap<>(); try { ResponseCtx result = pdpEngine.evaluateRequest(request.getXacmlRequest()); response.put("decision", result.getResults()[0].getDecision().toString()); response.put("success", true); // Add additional response details Map<String, String> details = new HashMap<>(); details.put("policyId", result.getResults()[0].getPolicyIdentifier().toString()); response.put("details", details); return ResponseEntity.ok(response); } catch (Exception e) { response.put("success", false); response.put("error", e.getMessage()); return ResponseEntity.badRequest().body(response); } } @PostMapping("/policy") public ResponseEntity<Map<String, Object>> addPolicy(@RequestBody PolicyRequest policyRequest) { Map<String, Object> response = new HashMap<>(); try { pap.addPolicy(policyRequest.getPolicyId(), policyRequest.getPolicyXml()); response.put("success", true); response.put("message", "Policy added successfully"); return ResponseEntity.ok(response); } catch (Exception e) { response.put("success", false); response.put("error", e.getMessage()); return ResponseEntity.badRequest().body(response); } } @GetMapping("/policy/{policyId}") public ResponseEntity<Map<String, Object>> getPolicy(@PathVariable String policyId) { Map<String, Object> response = new HashMap<>(); try { org.wso2.balana.Policy policy = pap.getPolicy(policyId); if (policy != null) { response.put("success", true); response.put("policy", policy.encode()); } else { response.put("success", false); response.put("error", "Policy not found"); } return ResponseEntity.ok(response); } catch (Exception e) { response.put("success", false); response.put("error", e.getMessage()); return ResponseEntity.badRequest().body(response); } } // Request DTOs public static class AccessRequest { private String xacmlRequest; public String getXacmlRequest() { return xacmlRequest; } public void setXacmlRequest(String xacmlRequest) { this.xacmlRequest = xacmlRequest; } } public static class PolicyRequest { private String policyId; private String policyXml; public String getPolicyId() { return policyId; } public void setPolicyId(String policyId) { this.policyId = policyId; } public String getPolicyXml() { return policyXml; } public void setPolicyXml(String policyXml) { this.policyXml = policyXml; } } } Advanced Features
Policy Validation Service
package com.xacml.validation; import org.wso2.balana.Policy; import org.wso2.balana.PolicySet; import org.wso2.balana.parser.PolicyBuilder; import org.wso2.balana.parser.PolicySetBuilder; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class PolicyValidationService { /** * Validate XACML policy syntax */ public ValidationResult validatePolicySyntax(String policyXml) { try { Policy policy = Policy.getInstance(new ByteArrayInputStream(policyXml.getBytes())); return new ValidationResult(true, "Policy syntax is valid", policy.getVersion()); } catch (Exception e) { return new ValidationResult(false, "Invalid policy syntax: " + e.getMessage(), null); } } /** * Validate policy set syntax */ public ValidationResult validatePolicySetSyntax(String policySetXml) { try { PolicySet policySet = PolicySet.getInstance(new ByteArrayInputStream(policySetXml.getBytes())); return new ValidationResult(true, "PolicySet syntax is valid", policySet.getVersion()); } catch (Exception e) { return new ValidationResult(false, "Invalid PolicySet syntax: " + e.getMessage(), null); } } /** * Check for policy conflicts */ public List<Conflict> detectConflicts(List<Policy> policies) { List<Conflict> conflicts = new ArrayList<>(); // Simple conflict detection based on target overlaps for (int i = 0; i < policies.size(); i++) { for (int j = i + 1; j < policies.size(); j++) { if (policiesOverlap(policies.get(i), policies.get(j))) { conflicts.add(new Conflict( policies.get(i).getId().toString(), policies.get(j).getId().toString(), "Target overlap detected" )); } } } return conflicts; } private boolean policiesOverlap(Policy policy1, Policy policy2) { // Simplified overlap detection // In practice, this would involve complex target matching logic return policy1.getTarget().equals(policy2.getTarget()); } public static class ValidationResult { private final boolean valid; private final String message; private final String version; public ValidationResult(boolean valid, String message, String version) { this.valid = valid; this.message = message; this.version = version; } public boolean isValid() { return valid; } public String getMessage() { return message; } public String getVersion() { return version; } } public static class Conflict { private final String policyId1; private final String policyId2; private final String description; public Conflict(String policyId1, String policyId2, String description) { this.policyId1 = policyId1; this.policyId2 = policyId2; this.description = description; } public String getPolicyId1() { return policyId1; } public String getPolicyId2() { return policyId2; } public String getDescription() { return description; } } } Testing the Implementation
JUnit Tests
package com.xacml.test; import com.xacml.engine.BasicPDPEngine; import com.xacml.pap.PolicyAdministrationPoint; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.wso2.balana.Decision; import java.util.HashMap; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; class XACMLEngineTest { private BasicPDPEngine pdpEngine; private PolicyAdministrationPoint pap; @BeforeEach void setUp() throws Exception { pdpEngine = new BasicPDPEngine(); pap = new PolicyAdministrationPoint(); // Load sample policies pap.addPolicy("role-policy", SamplePolicies.ROLE_BASED_POLICY); pdpEngine.loadPolicy(SamplePolicies.ROLE_BASED_POLICY); } @Test void testRoleBasedAccess() throws Exception { // Test admin access Decision decision1 = pdpEngine.evaluateAccess( "user1", "admin", "document1", "write", new HashMap<>()); assertEquals(Decision.PERMIT, decision1); // Test user read access Decision decision2 = pdpEngine.evaluateAccess( "user2", "user", "document1", "read", new HashMap<>()); assertEquals(Decision.PERMIT, decision2); // Test user write access (should be denied) Decision decision3 = pdpEngine.evaluateAccess( "user2", "user", "document1", "write", new HashMap<>()); assertEquals(Decision.DENY, decision3); } @Test void testPolicyManagement() throws Exception { // Test policy addition pap.addPolicy("test-policy", SamplePolicies.TIME_BASED_POLICY); assertNotNull(pap.getPolicy("test-policy")); // Test policy removal pap.removePolicy("test-policy"); assertNull(pap.getPolicy("test-policy")); } } Performance Optimization
Caching PDP
package com.xacml.engine; import org.wso2.balana.ctx.xacml3.RequestCtx; import org.wso2.balana.ctx.xacml3.ResponseCtx; import java.util.concurrent.*; public class CachedPDPEngine extends BasicPDPEngine { private final Cache<RequestCtx, ResponseCtx> decisionCache; private final long cacheTimeoutMs; public CachedPDPEngine(long cacheTimeoutMs, int maxCacheSize) { super(); this.cacheTimeoutMs = cacheTimeoutMs; this.decisionCache = new Cache<>(maxCacheSize, cacheTimeoutMs); } @Override public ResponseCtx evaluateRequest(String xacmlRequest) throws Exception { RequestCtx request = RequestCtx.getInstance( new ByteArrayInputStream(xacmlRequest.getBytes())); // Check cache first ResponseCtx cachedResponse = decisionCache.get(request); if (cachedResponse != null) { return cachedResponse; } // Evaluate and cache result ResponseCtx response = super.evaluateRequest(xacmlRequest); decisionCache.put(request, response); return response; } /** * Simple LRU cache implementation */ private static class Cache<K, V> { private final ConcurrentHashMap<K, V> map; private final ConcurrentLinkedQueue<K> queue; private final int maxSize; private final long timeoutMs; public Cache(int maxSize, long timeoutMs) { this.maxSize = maxSize; this.timeoutMs = timeoutMs; this.map = new ConcurrentHashMap<>(); this.queue = new ConcurrentLinkedQueue<>(); } public V get(K key) { return map.get(key); } public void put(K key, V value) { if (map.size() >= maxSize) { K oldestKey = queue.poll(); if (oldestKey != null) { map.remove(oldestKey); } } queue.add(key); map.put(key, value); } } } Conclusion
This comprehensive XACML policy engine implementation in Java provides:
- Complete PDP engine with Balana integration
- Policy administration for managing XACML policies
- Attribute-based authorization with custom PIP
- RESTful API for integration with applications
- Policy validation and conflict detection
- Performance optimization with caching
The engine supports complex authorization scenarios including role-based access control, attribute-based rules, time-based restrictions, and custom business logic. It can be integrated into enterprise applications to provide fine-grained, centralized authorization management following the XACML standard.