System Overview The privilege system combines Role-Based Access Control (RBAC) with Attribute-Based Access Control (ABAC) to provide fine-grained authorization capabilities. This hybrid approach leverages the simplicity of RBAC for common scenarios while utilizing ABAC’s flexibility for complex, context-aware access decisions.
flowchart TB
A[Client Request] --> B[PrivilegeFilterSDK]
B --> C{Cache Check}
C -->|Hit| D[Return Cached Result]
C -->|Miss| E[PrivilegeService]
E --> F[RBAC Engine]
E --> G[ABAC Engine]
F --> H[Role Evaluation]
G --> I[Attribute Evaluation]
H --> J[Access Decision]
I --> J
J --> K[Update Cache]
K --> L[Return Result]
M[PrivilegeWebUI] --> E
N[Database] --> E
O[Redis Cache] --> C
P[Local Cache] --> C
Interview Question : Why combine RBAC and ABAC instead of using one approach?
Answer : RBAC provides simplicity and performance for common role-based scenarios (90% of use cases), while ABAC handles complex, context-dependent decisions (10% of use cases). This hybrid approach balances performance, maintainability, and flexibility. Pure ABAC would be overkill for simple role checks, while pure RBAC lacks the granularity needed for dynamic, context-aware decisions.
Architecture Components PrivilegeService (Backend Core) The PrivilegeService acts as the central authority for all privilege-related operations, implementing both RBAC and ABAC engines.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Service public class PrivilegeService { @Autowired private RbacEngine rbacEngine; @Autowired private AbacEngine abacEngine; @Autowired private PrivilegeCacheManager cacheManager; public AccessDecision evaluateAccess (AccessRequest request) { String cacheKey = generateCacheKey(request); AccessDecision cached = cacheManager.get(cacheKey); if (cached != null ) { return cached; } AccessDecision rbacDecision = rbacEngine.evaluate(request); if (rbacDecision.isExplicitDeny()) { cacheManager.put(cacheKey, rbacDecision, 300 ); return rbacDecision; } AccessDecision abacDecision = abacEngine.evaluate(request); AccessDecision finalDecision = combineDecisions(rbacDecision, abacDecision); cacheManager.put(cacheKey, finalDecision, 300 ); return finalDecision; } }
Key APIs :
POST /api/v1/privileges/evaluate
- Evaluate access permissions
GET /api/v1/roles/{userId}
- Get user roles
POST /api/v1/roles/{userId}
- Assign roles to user
GET /api/v1/permissions/{roleId}
- Get role permissions
POST /api/v1/policies
- Create ABAC policies
PrivilegeWebUI (Administrative Interface) A React-based administrative interface for managing users, roles, and permissions.
graph LR
A[User Management] --> B[Role Assignment]
B --> C[Permission Matrix]
C --> D[Policy Editor]
D --> E[Audit Logs]
F[Dashboard] --> G[Real-time Metrics]
G --> H[Access Patterns]
H --> I[Security Alerts]
Key Features :
User Management : Search, filter, and manage user accounts
Role Matrix : Visual representation of role-permission mappings
Policy Builder : Drag-and-drop interface for creating ABAC policies
Audit Dashboard : Real-time access logs and security metrics
Bulk Operations : Import/export users and roles via CSV
Use Case Example : An administrator needs to grant temporary access to a contractor for a specific project. Using the WebUI, they can:
Create a time-bound role “Project_Contractor_Q2”
Assign specific permissions (read project files, submit reports)
Set expiration date and IP restrictions
Monitor access patterns through the dashboard
PrivilegeFilterSDK (Integration Component) A lightweight SDK that integrates with microservices to provide seamless privilege checking.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Component public class PrivilegeFilter implements Filter { @Autowired private PrivilegeClient privilegeClient; @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; UserContext userContext = extractUserContext(httpRequest); AccessRequest accessRequest = AccessRequest.builder() .userId(userContext.getUserId()) .resource(httpRequest.getRequestURI()) .action(httpRequest.getMethod()) .environment(buildEnvironmentAttributes(httpRequest)) .build(); AccessDecision decision = privilegeClient.evaluateAccess(accessRequest); if (decision.isPermitted()) { chain.doFilter(request, response); } else { sendUnauthorizedResponse(response, decision.getReason()); } } private Map<String, Object> buildEnvironmentAttributes (HttpServletRequest request) { return Map.of( "ip_address" , getClientIP(request), "user_agent" , request.getHeader("User-Agent" ), "time_of_day" , LocalTime.now().getHour(), "request_size" , request.getContentLength() ); } }
Interview Question : How do you handle the performance impact of privilege checking on every request?
Answer : We implement a three-tier caching strategy: local cache (L1) for frequently accessed decisions, Redis (L2) for shared cache across instances, and database (L3) as the source of truth. Additionally, we use async batch loading for role hierarchies and implement circuit breakers to fail-open during service degradation.
Three-Tier Caching Architecture
flowchart TD
A[Request] --> B[L1: Local Cache]
B -->|Miss| C[L2: Redis Cache]
C -->|Miss| D[L3: Database]
D --> E[Privilege Calculation]
E --> F[Update All Cache Layers]
F --> G[Return Result]
H[Cache Invalidation] --> I[Event-Driven Updates]
I --> J[L1 Invalidation]
I --> K[L2 Invalidation]
Layer 1: Local Cache (Caffeine) 1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public class LocalCacheConfig { @Bean public Cache<String, AccessDecision> localPrivilegeCache () { return Caffeine.newBuilder() .maximumSize(10000 ) .expireAfterWrite(Duration.ofMinutes(5 )) .recordStats() .build(); } }
Characteristics :
Capacity : 10,000 entries per instance
TTL : 5 minutes
Hit Ratio : ~85% for frequent operations
Latency : <1ms
Layer 2: Distributed Cache (Redis) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Service public class RedisPrivilegeCache { @Autowired private RedisTemplate<String, AccessDecision> redisTemplate; public void cacheUserRoles (String userId, Set<Role> roles) { String key = "user:roles:" + userId; redisTemplate.opsForValue().set(key, roles, Duration.ofMinutes(30 )); } public void invalidateUserCache (String userId) { String pattern = "user:*:" + userId; Set<String> keys = redisTemplate.keys(pattern); if (!keys.isEmpty()) { redisTemplate.delete(keys); } } }
Characteristics :
Capacity : 1M entries cluster-wide
TTL : 30 minutes
Hit Ratio : ~70% for cache misses from L1
Latency : 1-5ms
Layer 3: Database (PostgreSQL) Persistent storage with optimized queries and indexing strategies.
Performance Metrics :
Overall Cache Hit Ratio : 95%
Average Response Time : 2ms (cached), 50ms (uncached)
Throughput : 10,000 requests/second per instance
Database Design Schema Overview
erDiagram
USER {
uuid id PK
string username UK
string email UK
timestamp created_at
timestamp updated_at
boolean is_active
}
ROLE {
uuid id PK
string name UK
string description
json attributes
timestamp created_at
boolean is_active
}
PERMISSION {
uuid id PK
string name UK
string resource
string action
json constraints
}
USER_ROLE {
uuid user_id FK
uuid role_id FK
timestamp assigned_at
timestamp expires_at
string assigned_by
}
ROLE_PERMISSION {
uuid role_id FK
uuid permission_id FK
}
ABAC_POLICY {
uuid id PK
string name UK
json policy_document
integer priority
boolean is_active
timestamp created_at
}
PRIVILEGE_AUDIT {
uuid id PK
uuid user_id FK
string resource
string action
string decision
json context
timestamp timestamp
}
USER ||--o{ USER_ROLE : has
ROLE ||--o{ USER_ROLE : assigned_to
ROLE ||--o{ ROLE_PERMISSION : has
PERMISSION ||--o{ ROLE_PERMISSION : granted_by
Detailed Table Schemas 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), username VARCHAR (50 ) UNIQUE NOT NULL , email VARCHAR (255 ) UNIQUE NOT NULL , password_hash VARCHAR (255 ) NOT NULL , created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP , updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP , last_login TIMESTAMP , is_active BOOLEAN DEFAULT true , attributes JSONB DEFAULT '{}' , CONSTRAINT users_username_check CHECK (length(username) >= 3 ), CONSTRAINT users_email_check CHECK (email ~ * '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' ) ); CREATE TABLE roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR (100 ) UNIQUE NOT NULL , description TEXT, parent_role_id UUID REFERENCES roles(id), attributes JSONB DEFAULT '{}' , created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP , is_active BOOLEAN DEFAULT true , CONSTRAINT roles_name_check CHECK (length(name) >= 2 ) ); CREATE TABLE permissions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR (100 ) UNIQUE NOT NULL , resource VARCHAR (100 ) NOT NULL , action VARCHAR (50 ) NOT NULL , constraints JSONB DEFAULT '{}' , created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP , UNIQUE (resource, action) ); CREATE TABLE user_roles ( user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE, assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP , expires_at TIMESTAMP , assigned_by UUID REFERENCES users(id), is_active BOOLEAN DEFAULT true , PRIMARY KEY (user_id, role_id), CONSTRAINT user_roles_expiry_check CHECK (expires_at IS NULL OR expires_at > assigned_at) ); CREATE TABLE abac_policies ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR (100 ) UNIQUE NOT NULL , policy_document JSONB NOT NULL , priority INTEGER DEFAULT 100 , is_active BOOLEAN DEFAULT true , created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP , created_by UUID REFERENCES users(id), CONSTRAINT abac_policies_priority_check CHECK (priority >= 0 AND priority <= 1000 ) ); CREATE TABLE privilege_audit ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id), resource VARCHAR (200 ) NOT NULL , action VARCHAR (50 ) NOT NULL , decision VARCHAR (20 ) NOT NULL CHECK (decision IN ('PERMIT' , 'DENY' , 'INDETERMINATE' )), context JSONB DEFAULT '{}' , timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP , processing_time_ms INTEGER ) PARTITION BY RANGE (timestamp );
Indexing Strategy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 CREATE INDEX idx_users_username ON users(username);CREATE INDEX idx_users_email ON users(email);CREATE INDEX idx_users_active ON users(is_active) WHERE is_active = true ;CREATE INDEX idx_roles_parent ON roles(parent_role_id);CREATE INDEX idx_user_roles_user ON user_roles(user_id);CREATE INDEX idx_user_roles_active ON user_roles(user_id, is_active) WHERE is_active = true ;CREATE INDEX idx_role_permissions_role ON role_permissions(role_id);CREATE INDEX idx_abac_policies_active ON abac_policies(is_active, priority) WHERE is_active = true ;CREATE INDEX idx_audit_user_time ON privilege_audit(user_id, timestamp DESC );CREATE INDEX idx_audit_resource ON privilege_audit(resource, timestamp DESC );CREATE INDEX idx_user_roles_expiry ON user_roles(user_id, expires_at) WHERE expires_at IS NOT NULL AND expires_at > CURRENT_TIMESTAMP ;
RBAC Engine Implementation Role Hierarchy Support 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Service public class RbacEngine { public Set<Role> getEffectiveRoles (String userId) { Set<Role> directRoles = userRoleRepository.findActiveRolesByUserId(userId); Set<Role> allRoles = new HashSet <>(directRoles); for (Role role : directRoles) { allRoles.addAll(getParentRoles(role)); } return allRoles; } private Set<Role> getParentRoles (Role role) { Set<Role> parents = new HashSet <>(); Role current = role; while (current.getParentRole() != null ) { current = current.getParentRole(); parents.add(current); } return parents; } public AccessDecision evaluate (AccessRequest request) { Set<Role> userRoles = getEffectiveRoles(request.getUserId()); for (Role role : userRoles) { if (roleHasPermission(role, request.getResource(), request.getAction())) { return AccessDecision.permit("RBAC: Role " + role.getName()); } } return AccessDecision.deny("RBAC: No matching role permissions" ); } }
Use Case Example : In a corporate environment, a “Senior Developer” role inherits permissions from “Developer” role, which inherits from “Employee” role. This hierarchy allows for efficient permission management without duplicating permissions across roles.
ABAC Engine Implementation Policy Structure ABAC policies are stored as JSON documents following the XACML-inspired structure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 { "id" : "time-based-access-policy" , "name" : "Business Hours Access Policy" , "version" : "1.0" , "target" : { "resources" : [ "api/financial/*" ] , "actions" : [ "GET" , "POST" ] } , "rules" : [ { "id" : "business-hours-rule" , "effect" : "Permit" , "condition" : { "and" : [ { "timeOfDay" : { "gte" : "09:00" , "lte" : "17:00" } } , { "dayOfWeek" : { "in" : [ "MONDAY" , "TUESDAY" , "WEDNESDAY" , "THURSDAY" , "FRIDAY" ] } } , { "userAttribute.department" : { "equals" : "FINANCE" } } ] } } ] }
Policy Evaluation Engine 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Service public class AbacEngine { @Autowired private PolicyRepository policyRepository; public AccessDecision evaluate (AccessRequest request) { List<AbacPolicy> applicablePolicies = findApplicablePolicies(request); applicablePolicies.sort((p1, p2) -> Integer.compare(p2.getPriority(), p1.getPriority())); for (AbacPolicy policy : applicablePolicies) { PolicyDecision decision = evaluatePolicy(policy, request); switch (decision.getEffect()) { case PERMIT: return AccessDecision.permit("ABAC: " + policy.getName()); case DENY: return AccessDecision.deny("ABAC: " + policy.getName()); case INDETERMINATE: continue ; } } return AccessDecision.deny("ABAC: No applicable policies" ); } private PolicyDecision evaluatePolicy (AbacPolicy policy, AccessRequest request) { try { PolicyDocument document = policy.getPolicyDocument(); if (!matchesTarget(document.getTarget(), request)) { return PolicyDecision.indeterminate(); } for (PolicyRule rule : document.getRules()) { if (evaluateCondition(rule.getCondition(), request)) { return PolicyDecision.of(rule.getEffect()); } } return PolicyDecision.indeterminate(); } catch (Exception e) { log.error("Error evaluating policy: " + policy.getId(), e); return PolicyDecision.indeterminate(); } } }
Interview Question : How do you handle policy conflicts in ABAC?
Answer : We use a priority-based approach where policies are evaluated in order of priority. The first policy that returns a definitive decision (PERMIT or DENY) wins. For same-priority policies, we use policy combining algorithms like “deny-overrides” or “permit-overrides” based on the security requirements. We also implement policy validation to detect potential conflicts at creation time.
Security Considerations Principle of Least Privilege 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Service public class PrivilegeAnalyzer { public PrivilegeAnalysisReport analyzeUserPrivileges (String userId) { Set<Permission> grantedPermissions = getAllUserPermissions(userId); Set<Permission> usedPermissions = getUsedPermissions(userId, Duration.ofDays(30 )); Set<Permission> unusedPermissions = new HashSet <>(grantedPermissions); unusedPermissions.removeAll(usedPermissions); return PrivilegeAnalysisReport.builder() .userId(userId) .totalGranted(grantedPermissions.size()) .totalUsed(usedPermissions.size()) .unusedPermissions(unusedPermissions) .riskScore(calculateRiskScore(unusedPermissions)) .recommendations(generateRecommendations(unusedPermissions)) .build(); } }
Audit and Compliance 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @EventListener public class PrivilegeAuditListener { @Async public void handleAccessDecision (AccessDecisionEvent event) { PrivilegeAuditRecord record = PrivilegeAuditRecord.builder() .userId(event.getUserId()) .resource(event.getResource()) .action(event.getAction()) .decision(event.getDecision()) .context(event.getContext()) .timestamp(Instant.now()) .processingTimeMs(event.getProcessingTime()) .build(); auditRepository.save(record); if (isSuspiciousActivity(record)) { alertService.sendSecurityAlert(record); } } }
Batch Processing for Role Assignments 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Service public class BulkPrivilegeService { @Transactional public void bulkAssignRoles (List<UserRoleAssignment> assignments) { validateAssignments(assignments); Map<String, List<UserRoleAssignment>> byUser = assignments.stream() .collect(Collectors.groupingBy(UserRoleAssignment::getUserId)); Lists.partition(new ArrayList <>(byUser.entrySet()), 100 ) .forEach(batch -> processBatch(batch)); Set<String> affectedUsers = assignments.stream() .map(UserRoleAssignment::getUserId) .collect(Collectors.toSet()); cacheManager.invalidateUsers(affectedUsers); } }
Query Optimization 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Repository public class OptimizedUserRoleRepository { @Query(value = """ SELECT r.* FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = :userId AND ur.is_active = true AND (ur.expires_at IS NULL OR ur.expires_at > CURRENT_TIMESTAMP) AND r.is_active = true """, nativeQuery = true) List<Role> findActiveRolesByUserId (@Param("userId") String userId) ; @Query(value = """ WITH RECURSIVE role_hierarchy AS ( SELECT id, name, parent_role_id, 0 as level FROM roles WHERE id IN :roleIds UNION ALL SELECT r.id, r.name, r.parent_role_id, rh.level + 1 FROM roles r JOIN role_hierarchy rh ON r.id = rh.parent_role_id WHERE rh.level < 10 ) SELECT DISTINCT * FROM role_hierarchy """, nativeQuery = true) List<Role> findRoleHierarchy (@Param("roleIds") Set<String> roleIds) ; }
Monitoring and Observability Metrics Collection 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Component public class PrivilegeMetrics { private final Counter accessDecisions = Counter.build() .name("privilege_access_decisions_total" ) .help("Total number of access decisions" ) .labelNames("decision" , "engine" ) .register(); private final Histogram decisionLatency = Histogram.build() .name("privilege_decision_duration_seconds" ) .help("Time spent on access decisions" ) .labelNames("engine" ) .register(); private final Gauge cacheHitRatio = Gauge.build() .name("privilege_cache_hit_ratio" ) .help("Cache hit ratio for privilege decisions" ) .labelNames("cache_layer" ) .register(); public void recordDecision (String decision, String engine, Duration duration) { accessDecisions.labels(decision, engine).inc(); decisionLatency.labels(engine).observe(duration.toMillis() / 1000.0 ); } }
Health Checks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Component public class PrivilegeHealthIndicator implements HealthIndicator { @Override public Health health () { try { long dbResponseTime = measureDatabaseHealth(); double cacheHitRatio = measureCacheHealth(); long avgDecisionTime = measureDecisionPerformance(); if (dbResponseTime > 100 || cacheHitRatio < 0.8 || avgDecisionTime > 50 ) { return Health.down() .withDetail("database_response_time" , dbResponseTime) .withDetail("cache_hit_ratio" , cacheHitRatio) .withDetail("avg_decision_time" , avgDecisionTime) .build(); } return Health.up() .withDetail("database_response_time" , dbResponseTime) .withDetail("cache_hit_ratio" , cacheHitRatio) .withDetail("avg_decision_time" , avgDecisionTime) .build(); } catch (Exception e) { return Health.down().withException(e).build(); } } }
Testing Strategy Unit Testing 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 @ExtendWith(MockitoExtension.class) class RbacEngineTest { @Mock private UserRoleRepository userRoleRepository; @InjectMocks private RbacEngine rbacEngine; @Test void shouldPermitAccessWhenUserHasRequiredRole () { String userId = "user123" ; Role developerRole = createRole("DEVELOPER" ); Permission readCodePermission = createPermission("code" , "read" ); developerRole.addPermission(readCodePermission); when (userRoleRepository.findActiveRolesByUserId(userId)) .thenReturn(Set.of(developerRole)); AccessRequest request = AccessRequest.builder() .userId(userId) .resource("code" ) .action("read" ) .build(); AccessDecision decision = rbacEngine.evaluate(request); assertThat(decision.isPermitted()).isTrue(); assertThat(decision.getReason()).contains("RBAC: Role DEVELOPER" ); } @Test void shouldInheritPermissionsFromParentRole () { String userId = "user123" ; Role employeeRole = createRole("EMPLOYEE" ); Role managerRole = createRole("MANAGER" , employeeRole); Permission basePermission = createPermission("dashboard" , "read" ); employeeRole.addPermission(basePermission); when (userRoleRepository.findActiveRolesByUserId(userId)) .thenReturn(Set.of(managerRole)); AccessRequest request = AccessRequest.builder() .userId(userId) .resource("dashboard" ) .action("read" ) .build(); AccessDecision decision = rbacEngine.evaluate(request); assertThat(decision.isPermitted()).isTrue(); } }
Integration Testing 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @SpringBootTest @Testcontainers class PrivilegeServiceIntegrationTest { @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer <>("postgres:13" ) .withDatabaseName("privilege_test" ) .withUsername("test" ) .withPassword("test" ); @Container static GenericContainer<?> redis = new GenericContainer <>("redis:6" ) .withExposedPorts(6379 ); @Test void shouldCacheAccessDecisions () { String userId = createTestUser(); String roleId = createTestRole(); assignRoleToUser(userId, roleId); AccessRequest request = AccessRequest.builder() .userId(userId) .resource("test-resource" ) .action("read" ) .build(); Instant start1 = Instant.now(); AccessDecision decision1 = privilegeService.evaluateAccess(request); Duration duration1 = Duration.between(start1, Instant.now()); Instant start2 = Instant.now(); AccessDecision decision2 = privilegeService.evaluateAccess(request); Duration duration2 = Duration.between(start2, Instant.now()); assertThat(decision1).isEqualTo(decision2); assertThat(duration2).isLessThan(duration1.dividedBy(2 )); } }
Deployment Considerations Kubernetes Deployment 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 apiVersion: apps/v1 kind: Deployment metadata: name: privilege-service spec: replicas: 3 selector: matchLabels: app: privilege-service template: metadata: labels: app: privilege-service spec: containers: - name: privilege-service image: privilege-service:latest ports: - containerPort: 8080 env: - name: SPRING_PROFILES_ACTIVE value: "production" - name: DATABASE_URL valueFrom: secretKeyRef: name: db-secret key: url - name: REDIS_URL valueFrom: secretKeyRef: name: redis-secret key: url resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 5 periodSeconds: 5
Database Migration Strategy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Component public class PrivilegeMigrationService { @EventListener @Order(1) public void onApplicationReady (ApplicationReadyEvent event) { if (isNewDeployment()) { createDefaultRolesAndPermissions(); } if (requiresDataMigration()) { migrateExistingData(); } validateSystemIntegrity(); } private void createDefaultRolesAndPermissions () { Role adminRole = roleService.createRole("SYSTEM_ADMIN" , "System Administrator" ); Permission allPermissions = permissionService.createPermission("*" , "*" ); roleService.assignPermission(adminRole.getId(), allPermissions.getId()); Role userRole = roleService.createRole("USER" , "Default User" ); Permission readProfile = permissionService.createPermission("profile" , "read" ); roleService.assignPermission(userRole.getId(), readProfile.getId()); } }
Real-World Use Cases Scenario : A multi-tenant SaaS platform needs to support different organizations with varying access control requirements.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Service public class TenantAwarePrivilegeService { public AccessDecision evaluateTenantAccess (AccessRequest request) { String tenantId = request.getContext().get("tenant_id" ); Set<Role> tenantRoles = getUserRolesInTenant(request.getUserId(), tenantId); AbacContext context = AbacContext.builder() .userAttributes(getUserAttributes(request.getUserId())) .resourceAttributes(getResourceAttributes(request.getResource())) .environmentAttributes(Map.of( "tenant_id" , tenantId, "subscription_level" , getTenantSubscription(tenantId), "data_residency" , getTenantDataResidency(tenantId) )) .build(); return evaluateWithContext(request, context); } }
ABAC Policy Example for Tenant Isolation :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "name" : "tenant-data-isolation-policy" , "target" : { "resources" : [ "api/data/*" ] } , "rules" : [ { "effect" : "Deny" , "condition" : { "not" : { "equals" : [ { "var" : "resource.tenant_id" } , { "var" : "user.tenant_id" } ] } } } ] }
Healthcare System HIPAA Compliance Scenario : A healthcare system requires strict access controls with audit trails for HIPAA compliance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Component public class HipaaPrivilegeEnforcer { @Autowired private PatientConsentService consentService; public AccessDecision evaluatePatientDataAccess (AccessRequest request) { String patientId = extractPatientId(request.getResource()); String providerId = request.getUserId(); if (!hasActiveTreatmentRelationship(providerId, patientId)) { return AccessDecision.deny("No active treatment relationship" ); } if (!consentService.hasValidConsent(patientId, providerId)) { return AccessDecision.deny("Patient consent required" ); } if (isEmergencyAccess(request)) { auditService.recordEmergencyAccess(request); return AccessDecision.permit("Emergency access granted" ); } return super .evaluate(request); } }
Financial Services Regulatory Compliance Scenario : A financial institution needs to implement segregation of duties and time-bound access for regulatory compliance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Service public class FinancialPrivilegeService { public AccessDecision evaluateFinancialTransaction (AccessRequest request) { BigDecimal amount = extractTransactionAmount(request); if (amount.compareTo(new BigDecimal ("10000" )) > 0 ) { return evaluateDualApproval(request); } if (isTradingResource(request.getResource())) { return evaluateTradingHours(request); } return super .evaluate(request); } private AccessDecision evaluateDualApproval (AccessRequest request) { String transactionId = request.getContext().get("transaction_id" ); Optional<Approval> existingApproval = approvalService .findPendingApproval(transactionId); if (existingApproval.isPresent() && !existingApproval.get().getApproverId().equals(request.getUserId())) { return AccessDecision.permit("Dual approval satisfied" ); } approvalService.createPendingApproval(transactionId, request.getUserId()); return AccessDecision.deny("Awaiting second approval" ); } }
Advanced Features Dynamic Permission Discovery 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Service public class DynamicPermissionService { @EventListener public void discoverPermissions (ApplicationReadyEvent event) { ApplicationContext context = event.getApplicationContext(); context.getBeansWithAnnotation(RestController.class).values() .forEach(this ::scanControllerForPermissions); } private void scanControllerForPermissions (Object controller) { Class<?> clazz = AopUtils.getTargetClass(controller); RequestMapping classMapping = clazz.getAnnotation(RequestMapping.class); for (Method method : clazz.getDeclaredMethods()) { RequiresPermission permissionAnnotation = method.getAnnotation(RequiresPermission.class); if (permissionAnnotation != null ) { String resource = buildResourcePath(classMapping, method); String action = extractAction(method); permissionService.registerPermission( permissionAnnotation.value(), resource, action, permissionAnnotation.description() ); } } } } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresPermission { String value () ; String description () default "" ; String[] conditions() default {}; }
Policy Templates 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Service public class PolicyTemplateService { private static final Map<String, PolicyTemplate> TEMPLATES = Map.of( "time_restricted" , new TimeRestrictedTemplate (), "ip_restricted" , new IpRestrictedTemplate (), "department_scoped" , new DepartmentScopedTemplate (), "temporary_access" , new TemporaryAccessTemplate () ); public AbacPolicy createPolicyFromTemplate (String templateName, Map<String, Object> parameters) { PolicyTemplate template = TEMPLATES.get(templateName); if (template == null ) { throw new IllegalArgumentException ("Unknown template: " + templateName); } return template.generatePolicy(parameters); } } public class TimeRestrictedTemplate implements PolicyTemplate { @Override public AbacPolicy generatePolicy (Map<String, Object> params) { String startTime = (String) params.get("start_time" ); String endTime = (String) params.get("end_time" ); List<String> allowedDays = (List<String>) params.get("allowed_days" ); PolicyDocument document = PolicyDocument.builder() .rule(PolicyRule.builder() .effect(Effect.PERMIT) .condition(buildTimeCondition(startTime, endTime, allowedDays)) .build()) .build(); return AbacPolicy.builder() .name("time-restricted-" + UUID.randomUUID()) .policyDocument(document) .priority(100 ) .build(); } }
Machine Learning Integration 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Service public class AnomalousAccessDetector { @Autowired private AccessPatternAnalyzer patternAnalyzer; @EventListener @Async public void analyzeAccessPattern (AccessDecisionEvent event) { AccessPattern pattern = AccessPattern.builder() .userId(event.getUserId()) .resource(event.getResource()) .action(event.getAction()) .timestamp(event.getTimestamp()) .ipAddress(event.getContext().get("ip_address" )) .userAgent(event.getContext().get("user_agent" )) .build(); double anomalyScore = patternAnalyzer.calculateAnomalyScore(pattern); if (anomalyScore > 0.8 ) { SecurityAlert alert = SecurityAlert.builder() .userId(event.getUserId()) .alertType("ANOMALOUS_ACCESS" ) .severity(Severity.HIGH) .description("Unusual access pattern detected" ) .anomalyScore(anomalyScore) .build(); alertService.sendAlert(alert); privilegeService.enableEnhancedMonitoring(event.getUserId(), Duration.ofHours(24 )); } } }
Load Testing Results Test Environment :
3 application instances (2 CPU, 4GB RAM each)
PostgreSQL (4 CPU, 8GB RAM)
Redis Cluster (3 nodes, 2GB RAM each)
Results :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Scenario: Mixed RBAC/ABAC evaluation ├── Concurrent Users: 1000 ├── Test Duration: 10 minutes ├── Total Requests: 2,847,293 ├── Average Response Time: 3.2ms ├── 95th Percentile: 8.5ms ├── 99th Percentile: 15.2ms ├── Error Rate: 0.02% └── Throughput: 4,745 RPS Cache Performance: ├── L1 Cache Hit Rate: 87.3% ├── L2 Cache Hit Rate: 11.8% ├── Database Queries: 0.9% └── Average Decision Time: 1.8ms (cached), 45ms (uncached)
Memory Usage Optimization 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Configuration public class MemoryOptimizationConfig { @Bean @ConditionalOnProperty(name = "privilege.optimization.memory", havingValue = "true") public PrivilegeService optimizedPrivilegeService () { return new MemoryOptimizedPrivilegeService (); } } public class MemoryOptimizedPrivilegeService extends PrivilegeService { private final Map<String, Permission> permissionFlyweights = new ConcurrentHashMap <>(); private final WeakHashMap<String, Set<Role>> userRoleCache = new WeakHashMap <>(); private final PolicyCompressor policyCompressor = new PolicyCompressor (); @Override protected Set<Permission> getUserPermissions (String userId) { return userRoleCache.computeIfAbsent(userId, this ::loadUserRoles) .stream() .flatMap(role -> role.getPermissions().stream()) .map(this ::getFlyweightPermission) .collect(Collectors.toSet()); } }
Interview Questions and Answers Architecture Questions Q: How would you handle a situation where the privilege service becomes unavailable?
A : Implement a circuit breaker pattern with graceful degradation:
Circuit Breaker : Use Hystrix or Resilience4j to detect service failures
Fallback Strategy : Cache recent decisions locally and apply “fail-secure” or “fail-open” policies based on criticality
Emergency Roles : Pre-configure emergency access roles that work offline
Async Recovery : Queue privilege decisions for later verification when service recovers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Component public class ResilientPrivilegeService { @CircuitBreaker(name = "privilege-service", fallbackMethod = "fallbackEvaluate") public AccessDecision evaluate (AccessRequest request) { return privilegeService.evaluateAccess(request); } public AccessDecision fallbackEvaluate (AccessRequest request, Exception ex) { AccessDecision cached = emergencyCache.get(request); if (cached != null ) { return cached; } if (isCriticalResource(request.getResource())) { return AccessDecision.deny("Service unavailable - fail secure" ); } else { return AccessDecision.permit("Service unavailable - fail open" ); } } }
Q: How do you ensure consistency across multiple instances of the privilege service?
A : Use distributed caching with event-driven invalidation:
Distributed Cache : Redis cluster for shared state
Event Sourcing : Publish privilege change events
Cache Invalidation : Listen to events and invalidate affected cache entries
Database Consistency : Use database transactions for critical updates
Eventual Consistency : Accept temporary inconsistency for better performance
Security Questions Q: How would you prevent privilege escalation attacks?
A : Implement multiple defense layers:
Principle of Least Privilege : Regular audits to remove unused permissions
Approval Workflows : Require approval for sensitive role assignments
Temporal Constraints : Time-bound permissions with automatic expiration
Delegation Restrictions : Prevent users from granting permissions they don’t have
Audit Monitoring : Real-time detection of unusual privilege changes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Service public class PrivilegeEscalationDetector { @EventListener public void detectEscalation (RoleAssignmentEvent event) { if (isPrivilegeEscalation(event)) { throw new SecurityException ("Potential privilege escalation detected" ); } } private boolean isPrivilegeEscalation (RoleAssignmentEvent event) { Set<Permission> assignerPermissions = getEffectivePermissions(event.getAssignerId()); Set<Permission> targetPermissions = getRolePermissions(event.getRoleId()); return !assignerPermissions.containsAll(targetPermissions); } }
Q: How would you optimize the system for 100,000+ concurrent users?
A : Multi-layered optimization approach:
Horizontal Scaling : Auto-scaling groups with load balancers
Caching Strategy : 4-tier caching (Browser → CDN → App Cache → Database)
Database Optimization : Read replicas, connection pooling, query optimization
Async Processing : Queue heavy operations like audit logging
Pre-computation : Background jobs to pre-calculate common decisions
Troubleshooting Guide Common Issues and Solutions Issue : High latency on privilege decisions
1 2 3 4 5 6 7 8 curl http://localhost:8080/actuator/metrics/cache.gets | jq EXPLAIN ANALYZE SELECT * FROM user_roles WHERE user_id = 'user123' ; tail -f /var/log/privilege-service.log | grep "Cache miss"
Solution : Implement cache warming and query optimization
Issue : Memory leaks in long-running instances
1 2 3 4 5 6 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var /log/heapdumps/ jcmd <pid> GC.run_finalization
Solution : Use weak references for rarely accessed data and implement cache size limits
External Resources Standards and Specifications
Implementation References
This comprehensive design provides a production-ready privilege system that balances security, performance, and maintainability while addressing real-world enterprise requirements.