Spring Security ACL provides instance-based security for domain objects through a comprehensive Access Control List implementation
—
Spring Security ACL includes sophisticated caching and performance optimization features to ensure efficient permission checking even with complex ACL hierarchies and large datasets.
ACL performance optimization includes:
AclCache - Core caching interface for ACL dataSpringCacheBasedAclCache - Integration with Spring's caching frameworkAclPermissionCacheOptimizer - Batch loading for collection filteringThe AclCache interface provides caching abstraction for ACL data:
package org.springframework.security.acls.model;
public interface AclCache {
// Clear entire cache
void clearCache();
// Remove specific ACL from cache
void evictFromCache(ObjectIdentity objectIdentity);
// Remove ACL for specific SIDs from cache
void evictFromCache(Serializable pk);
// Retrieve ACL from cache
MutableAcl getFromCache(ObjectIdentity objectIdentity);
// Retrieve ACL by primary key from cache
MutableAcl getFromCache(Serializable pk);
// Store ACL in cache
void putInCache(MutableAcl acl);
}Production-ready cache implementation using Spring's caching framework:
package org.springframework.security.acls.domain;
public class SpringCacheBasedAclCache implements AclCache {
public SpringCacheBasedAclCache(
Cache cache,
PermissionGrantingStrategy permissionGrantingStrategy,
AclAuthorizationStrategy aclAuthorizationStrategy
);
// Cache configuration methods
public void clearCache();
public void evictFromCache(ObjectIdentity objectIdentity);
public void evictFromCache(Serializable pk);
public MutableAcl getFromCache(ObjectIdentity objectIdentity);
public MutableAcl getFromCache(Serializable pk);
public void putInCache(MutableAcl acl);
}@Configuration
@EnableCaching
public class AclCacheConfig {
@Bean
public AclCache aclCache() {
return new SpringCacheBasedAclCache(
cacheManager().getCache("aclCache"),
permissionGrantingStrategy(),
aclAuthorizationStrategy()
);
}
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager("aclCache");
return cacheManager;
}
@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(auditLogger());
}
@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_ADMIN")
);
}
}@Configuration
@EnableCaching
public class AdvancedAclCacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("aclCache");
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(10000) // Limit cache size
.expireAfterWrite(Duration.ofMinutes(30)) // TTL for entries
.expireAfterAccess(Duration.ofMinutes(10)) // Idle timeout
.recordStats() // Enable metrics
.removalListener((key, value, cause) -> {
log.debug("Evicted ACL cache entry: key={}, cause={}", key, cause);
}));
return cacheManager;
}
@Bean
public AclCache aclCache() {
SpringCacheBasedAclCache cache = new SpringCacheBasedAclCache(
cacheManager().getCache("aclCache"),
permissionGrantingStrategy(),
aclAuthorizationStrategy()
);
return cache;
}
}Optimizes permission checking for collections by batching ACL lookups:
package org.springframework.security.acls;
public class AclPermissionCacheOptimizer implements PermissionCacheOptimizer {
private final AclService aclService;
private final ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy;
private final SidRetrievalStrategy sidRetrievalStrategy;
public AclPermissionCacheOptimizer(AclService aclService);
// Batch load ACLs for a collection of objects
@Override
public void cachePermissionsFor(Authentication authentication,
Collection<?> objects);
}@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
@Bean
public PermissionEvaluator permissionEvaluator(AclService aclService) {
return new AclPermissionEvaluator(aclService);
}
@Bean
public PermissionCacheOptimizer permissionCacheOptimizer(AclService aclService) {
return new AclPermissionCacheOptimizer(aclService);
}
}
// Usage in service methods
@Service
public class DocumentService {
@Autowired
private PermissionCacheOptimizer cacheOptimizer;
@PostFilter("hasPermission(filterObject, 'READ')")
public List<Document> getAllDocuments(Authentication auth) {
List<Document> documents = documentRepository.findAll();
// Pre-load permissions for all documents in a single batch query
cacheOptimizer.cachePermissionsFor(auth, documents);
return documents; // PostFilter will use cached data
}
}Always provide SID lists to reduce data transfer:
@Service
public class OptimizedSecurityService {
@Autowired
private AclService aclService;
@Autowired
private SidRetrievalStrategy sidRetrievalStrategy;
public boolean hasPermission(Object domainObject, Permission permission, Authentication auth) {
ObjectIdentity identity = new ObjectIdentityImpl(domainObject);
List<Sid> sids = sidRetrievalStrategy.getSids(auth);
try {
// Only load ACL entries for current user's SIDs
Acl acl = aclService.readAclById(identity, sids);
return acl.isGranted(Arrays.asList(permission), sids, false);
} catch (NotFoundException e) {
return false;
}
}
}Process multiple objects efficiently:
public Map<Document, Boolean> checkBulkPermissions(List<Document> documents, Authentication auth) {
List<ObjectIdentity> identities = documents.stream()
.map(doc -> new ObjectIdentityImpl(Document.class, doc.getId()))
.collect(Collectors.toList());
List<Sid> sids = sidRetrievalStrategy.getSids(auth);
List<Permission> readPermission = Arrays.asList(BasePermission.READ);
try {
// Single batch query for all ACLs
Map<ObjectIdentity, Acl> acls = aclService.readAclsById(identities, sids);
return documents.stream()
.collect(Collectors.toMap(
Function.identity(),
doc -> {
ObjectIdentity identity = new ObjectIdentityImpl(Document.class, doc.getId());
Acl acl = acls.get(identity);
return acl != null && acl.isGranted(readPermission, sids, false);
}
));
} catch (NotFoundException e) {
return documents.stream()
.collect(Collectors.toMap(Function.identity(), doc -> false));
}
}Optimize SQL queries for your specific database:
@Bean
public LookupStrategy lookupStrategy() {
BasicLookupStrategy strategy = new BasicLookupStrategy(
dataSource(),
aclCache(),
aclAuthorizationStrategy(),
permissionGrantingStrategy()
);
// Customize SQL for better performance
strategy.setSelectClause(
"SELECT obj.object_id_identity, " +
"class.class, " +
"sid.sid, sid.principal, " +
"acl.entries_inheriting, acl.id as acl_id, " +
"acl.parent_object, acl.owner_sid, " +
"entry.id, entry.mask, entry.granting, " +
"entry.audit_success, entry.audit_failure "
);
// Add database-specific optimizations
strategy.setLookupObjectIdentitiesWhereClause("(obj.object_id_identity = ? and class.class = ?)");
return strategy;
}Configure database connections for ACL workloads:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost/acl_db");
config.setUsername("acluser");
config.setPassword("password");
// Optimize for ACL read-heavy workloads
config.setMaximumPoolSize(25);
config.setMinimumIdle(10);
config.setConnectionTimeout(20000);
config.setIdleTimeout(300000);
config.setMaxLifetime(1200000);
// Optimize for batch queries
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("useServerPrepStmts", "true");
return new HikariDataSource(config);
}
}Monitor cache performance:
@Component
public class AclCacheMetrics {
private final CacheManager cacheManager;
private final MeterRegistry meterRegistry;
@EventListener
@Async
public void handleCacheEvictEvent(CacheEvictEvent event) {
meterRegistry.counter("acl.cache.evictions",
"cache", event.getCacheName()).increment();
}
@Scheduled(fixedRate = 60000) // Every minute
public void recordCacheStats() {
Cache aclCache = cacheManager.getCache("aclCache");
if (aclCache instanceof CaffeineCache) {
com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache =
((CaffeineCache) aclCache).getNativeCache();
CacheStats stats = nativeCache.stats();
meterRegistry.gauge("acl.cache.size", nativeCache.estimatedSize());
meterRegistry.gauge("acl.cache.hit.rate", stats.hitRate());
meterRegistry.gauge("acl.cache.miss.rate", stats.missRate());
meterRegistry.gauge("acl.cache.eviction.count", stats.evictionCount());
}
}
}Track ACL operation performance:
@Component
public class AclPerformanceMonitor {
private final MeterRegistry meterRegistry;
@EventListener
public void handleAclServiceCall(AclServiceCallEvent event) {
Timer.Sample sample = Timer.start(meterRegistry);
sample.stop(Timer.builder("acl.service.call")
.tag("method", event.getMethodName())
.tag("object.count", String.valueOf(event.getObjectCount()))
.register(meterRegistry));
}
}Pre-populate cache with frequently accessed ACLs:
@Component
public class AclCacheWarmer {
@Autowired
private AclService aclService;
@EventListener(ApplicationReadyEvent.class)
public void warmUpCache() {
// Load frequently accessed ACLs on startup
List<ObjectIdentity> frequentlyAccessed = getFrequentlyAccessedObjects();
try {
aclService.readAclsById(frequentlyAccessed);
} catch (NotFoundException e) {
log.warn("Some ACLs not found during cache warming", e);
}
}
}Invalidate cache appropriately when ACLs change:
@Service
public class CacheAwareMutableAclService {
@Autowired
private MutableAclService mutableAclService;
@Autowired
private AclCache aclCache;
public MutableAcl updateAcl(MutableAcl acl) {
MutableAcl updated = mutableAclService.updateAcl(acl);
// Invalidate cache entry
aclCache.putInCache(updated);
// Also invalidate parent ACLs if inheritance is involved
if (updated.getParentAcl() != null) {
aclCache.evictFromCache(updated.getParentAcl().getObjectIdentity());
}
return updated;
}
}Monitor and manage cache memory usage:
@Configuration
public class AclCacheMemoryConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager("aclCache");
manager.setCaffeine(Caffeine.newBuilder()
.maximumWeight(100_000_000) // 100MB
.weigher((key, value) -> {
// Custom weigher for ACL objects
if (value instanceof AclImpl) {
AclImpl acl = (AclImpl) value;
return 1000 + (acl.getEntries().size() * 100);
}
return 1000;
})
.recordStats());
return manager;
}
}Proper caching and performance optimization is essential for production ACL deployments. The next step is understanding configuration and setup to integrate these performance features into your application.
Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-security--spring-security-acl