0
# Caching & Performance Optimization
1
2
Spring Security ACL includes sophisticated caching and performance optimization features to ensure efficient permission checking even with complex ACL hierarchies and large datasets.
3
4
## Overview
5
6
ACL performance optimization includes:
7
8
- **`AclCache`** - Core caching interface for ACL data
9
- **`SpringCacheBasedAclCache`** - Integration with Spring's caching framework
10
- **`AclPermissionCacheOptimizer`** - Batch loading for collection filtering
11
- **Lookup Strategy Optimization** - SQL query customization
12
- **SID Filtering** - Reduce data transfer by loading only relevant entries
13
14
## AclCache Interface
15
16
The `AclCache` interface provides caching abstraction for ACL data:
17
18
```java { .api }
19
package org.springframework.security.acls.model;
20
21
public interface AclCache {
22
23
// Clear entire cache
24
void clearCache();
25
26
// Remove specific ACL from cache
27
void evictFromCache(ObjectIdentity objectIdentity);
28
29
// Remove ACL for specific SIDs from cache
30
void evictFromCache(Serializable pk);
31
32
// Retrieve ACL from cache
33
MutableAcl getFromCache(ObjectIdentity objectIdentity);
34
35
// Retrieve ACL by primary key from cache
36
MutableAcl getFromCache(Serializable pk);
37
38
// Store ACL in cache
39
void putInCache(MutableAcl acl);
40
}
41
```
42
43
## SpringCacheBasedAclCache
44
45
Production-ready cache implementation using Spring's caching framework:
46
47
```java { .api }
48
package org.springframework.security.acls.domain;
49
50
public class SpringCacheBasedAclCache implements AclCache {
51
52
public SpringCacheBasedAclCache(
53
Cache cache,
54
PermissionGrantingStrategy permissionGrantingStrategy,
55
AclAuthorizationStrategy aclAuthorizationStrategy
56
);
57
58
// Cache configuration methods
59
public void clearCache();
60
public void evictFromCache(ObjectIdentity objectIdentity);
61
public void evictFromCache(Serializable pk);
62
public MutableAcl getFromCache(ObjectIdentity objectIdentity);
63
public MutableAcl getFromCache(Serializable pk);
64
public void putInCache(MutableAcl acl);
65
}
66
```
67
68
### Basic Cache Configuration
69
70
```java { .api }
71
@Configuration
72
@EnableCaching
73
public class AclCacheConfig {
74
75
@Bean
76
public AclCache aclCache() {
77
return new SpringCacheBasedAclCache(
78
cacheManager().getCache("aclCache"),
79
permissionGrantingStrategy(),
80
aclAuthorizationStrategy()
81
);
82
}
83
84
@Bean
85
public CacheManager cacheManager() {
86
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager("aclCache");
87
return cacheManager;
88
}
89
90
@Bean
91
public PermissionGrantingStrategy permissionGrantingStrategy() {
92
return new DefaultPermissionGrantingStrategy(auditLogger());
93
}
94
95
@Bean
96
public AclAuthorizationStrategy aclAuthorizationStrategy() {
97
return new AclAuthorizationStrategyImpl(
98
new SimpleGrantedAuthority("ROLE_ADMIN")
99
);
100
}
101
}
102
```
103
104
### Advanced Cache Configuration
105
106
```java { .api }
107
@Configuration
108
@EnableCaching
109
public class AdvancedAclCacheConfig {
110
111
@Bean
112
public CacheManager cacheManager() {
113
CaffeineCacheManager cacheManager = new CaffeineCacheManager("aclCache");
114
cacheManager.setCaffeine(Caffeine.newBuilder()
115
.maximumSize(10000) // Limit cache size
116
.expireAfterWrite(Duration.ofMinutes(30)) // TTL for entries
117
.expireAfterAccess(Duration.ofMinutes(10)) // Idle timeout
118
.recordStats() // Enable metrics
119
.removalListener((key, value, cause) -> {
120
log.debug("Evicted ACL cache entry: key={}, cause={}", key, cause);
121
}));
122
return cacheManager;
123
}
124
125
@Bean
126
public AclCache aclCache() {
127
SpringCacheBasedAclCache cache = new SpringCacheBasedAclCache(
128
cacheManager().getCache("aclCache"),
129
permissionGrantingStrategy(),
130
aclAuthorizationStrategy()
131
);
132
return cache;
133
}
134
}
135
```
136
137
## AclPermissionCacheOptimizer
138
139
Optimizes permission checking for collections by batching ACL lookups:
140
141
```java { .api }
142
package org.springframework.security.acls;
143
144
public class AclPermissionCacheOptimizer implements PermissionCacheOptimizer {
145
146
private final AclService aclService;
147
private final ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy;
148
private final SidRetrievalStrategy sidRetrievalStrategy;
149
150
public AclPermissionCacheOptimizer(AclService aclService);
151
152
// Batch load ACLs for a collection of objects
153
@Override
154
public void cachePermissionsFor(Authentication authentication,
155
Collection<?> objects);
156
}
157
```
158
159
### Using AclPermissionCacheOptimizer
160
161
```java { .api }
162
@Configuration
163
@EnableGlobalMethodSecurity(prePostEnabled = true)
164
public class MethodSecurityConfig {
165
166
@Bean
167
public PermissionEvaluator permissionEvaluator(AclService aclService) {
168
return new AclPermissionEvaluator(aclService);
169
}
170
171
@Bean
172
public PermissionCacheOptimizer permissionCacheOptimizer(AclService aclService) {
173
return new AclPermissionCacheOptimizer(aclService);
174
}
175
}
176
177
// Usage in service methods
178
@Service
179
public class DocumentService {
180
181
@Autowired
182
private PermissionCacheOptimizer cacheOptimizer;
183
184
@PostFilter("hasPermission(filterObject, 'READ')")
185
public List<Document> getAllDocuments(Authentication auth) {
186
List<Document> documents = documentRepository.findAll();
187
188
// Pre-load permissions for all documents in a single batch query
189
cacheOptimizer.cachePermissionsFor(auth, documents);
190
191
return documents; // PostFilter will use cached data
192
}
193
}
194
```
195
196
## Performance Optimization Strategies
197
198
### 1. SID Filtering
199
200
Always provide SID lists to reduce data transfer:
201
202
```java { .api }
203
@Service
204
public class OptimizedSecurityService {
205
206
@Autowired
207
private AclService aclService;
208
209
@Autowired
210
private SidRetrievalStrategy sidRetrievalStrategy;
211
212
public boolean hasPermission(Object domainObject, Permission permission, Authentication auth) {
213
ObjectIdentity identity = new ObjectIdentityImpl(domainObject);
214
List<Sid> sids = sidRetrievalStrategy.getSids(auth);
215
216
try {
217
// Only load ACL entries for current user's SIDs
218
Acl acl = aclService.readAclById(identity, sids);
219
return acl.isGranted(Arrays.asList(permission), sids, false);
220
} catch (NotFoundException e) {
221
return false;
222
}
223
}
224
}
225
```
226
227
### 2. Batch Loading
228
229
Process multiple objects efficiently:
230
231
```java { .api }
232
public Map<Document, Boolean> checkBulkPermissions(List<Document> documents, Authentication auth) {
233
List<ObjectIdentity> identities = documents.stream()
234
.map(doc -> new ObjectIdentityImpl(Document.class, doc.getId()))
235
.collect(Collectors.toList());
236
237
List<Sid> sids = sidRetrievalStrategy.getSids(auth);
238
List<Permission> readPermission = Arrays.asList(BasePermission.READ);
239
240
try {
241
// Single batch query for all ACLs
242
Map<ObjectIdentity, Acl> acls = aclService.readAclsById(identities, sids);
243
244
return documents.stream()
245
.collect(Collectors.toMap(
246
Function.identity(),
247
doc -> {
248
ObjectIdentity identity = new ObjectIdentityImpl(Document.class, doc.getId());
249
Acl acl = acls.get(identity);
250
return acl != null && acl.isGranted(readPermission, sids, false);
251
}
252
));
253
} catch (NotFoundException e) {
254
return documents.stream()
255
.collect(Collectors.toMap(Function.identity(), doc -> false));
256
}
257
}
258
```
259
260
### 3. Custom Lookup Strategy
261
262
Optimize SQL queries for your specific database:
263
264
```java { .api }
265
@Bean
266
public LookupStrategy lookupStrategy() {
267
BasicLookupStrategy strategy = new BasicLookupStrategy(
268
dataSource(),
269
aclCache(),
270
aclAuthorizationStrategy(),
271
permissionGrantingStrategy()
272
);
273
274
// Customize SQL for better performance
275
strategy.setSelectClause(
276
"SELECT obj.object_id_identity, " +
277
"class.class, " +
278
"sid.sid, sid.principal, " +
279
"acl.entries_inheriting, acl.id as acl_id, " +
280
"acl.parent_object, acl.owner_sid, " +
281
"entry.id, entry.mask, entry.granting, " +
282
"entry.audit_success, entry.audit_failure "
283
);
284
285
// Add database-specific optimizations
286
strategy.setLookupObjectIdentitiesWhereClause("(obj.object_id_identity = ? and class.class = ?)");
287
288
return strategy;
289
}
290
```
291
292
### 4. Connection Pool Optimization
293
294
Configure database connections for ACL workloads:
295
296
```java { .api }
297
@Configuration
298
public class DataSourceConfig {
299
300
@Bean
301
public DataSource dataSource() {
302
HikariConfig config = new HikariConfig();
303
config.setJdbcUrl("jdbc:postgresql://localhost/acl_db");
304
config.setUsername("acluser");
305
config.setPassword("password");
306
307
// Optimize for ACL read-heavy workloads
308
config.setMaximumPoolSize(25);
309
config.setMinimumIdle(10);
310
config.setConnectionTimeout(20000);
311
config.setIdleTimeout(300000);
312
config.setMaxLifetime(1200000);
313
314
// Optimize for batch queries
315
config.addDataSourceProperty("cachePrepStmts", "true");
316
config.addDataSourceProperty("prepStmtCacheSize", "250");
317
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
318
config.addDataSourceProperty("useServerPrepStmts", "true");
319
320
return new HikariDataSource(config);
321
}
322
}
323
```
324
325
## Monitoring and Metrics
326
327
### Cache Metrics
328
329
Monitor cache performance:
330
331
```java { .api }
332
@Component
333
public class AclCacheMetrics {
334
335
private final CacheManager cacheManager;
336
private final MeterRegistry meterRegistry;
337
338
@EventListener
339
@Async
340
public void handleCacheEvictEvent(CacheEvictEvent event) {
341
meterRegistry.counter("acl.cache.evictions",
342
"cache", event.getCacheName()).increment();
343
}
344
345
@Scheduled(fixedRate = 60000) // Every minute
346
public void recordCacheStats() {
347
Cache aclCache = cacheManager.getCache("aclCache");
348
if (aclCache instanceof CaffeineCache) {
349
com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache =
350
((CaffeineCache) aclCache).getNativeCache();
351
352
CacheStats stats = nativeCache.stats();
353
354
meterRegistry.gauge("acl.cache.size", nativeCache.estimatedSize());
355
meterRegistry.gauge("acl.cache.hit.rate", stats.hitRate());
356
meterRegistry.gauge("acl.cache.miss.rate", stats.missRate());
357
meterRegistry.gauge("acl.cache.eviction.count", stats.evictionCount());
358
}
359
}
360
}
361
```
362
363
### Performance Metrics
364
365
Track ACL operation performance:
366
367
```java { .api }
368
@Component
369
public class AclPerformanceMonitor {
370
371
private final MeterRegistry meterRegistry;
372
373
@EventListener
374
public void handleAclServiceCall(AclServiceCallEvent event) {
375
Timer.Sample sample = Timer.start(meterRegistry);
376
sample.stop(Timer.builder("acl.service.call")
377
.tag("method", event.getMethodName())
378
.tag("object.count", String.valueOf(event.getObjectCount()))
379
.register(meterRegistry));
380
}
381
}
382
```
383
384
## Best Practices
385
386
### 1. Cache Warming
387
388
Pre-populate cache with frequently accessed ACLs:
389
390
```java { .api }
391
@Component
392
public class AclCacheWarmer {
393
394
@Autowired
395
private AclService aclService;
396
397
@EventListener(ApplicationReadyEvent.class)
398
public void warmUpCache() {
399
// Load frequently accessed ACLs on startup
400
List<ObjectIdentity> frequentlyAccessed = getFrequentlyAccessedObjects();
401
402
try {
403
aclService.readAclsById(frequentlyAccessed);
404
} catch (NotFoundException e) {
405
log.warn("Some ACLs not found during cache warming", e);
406
}
407
}
408
}
409
```
410
411
### 2. Cache Invalidation Strategy
412
413
Invalidate cache appropriately when ACLs change:
414
415
```java { .api }
416
@Service
417
public class CacheAwareMutableAclService {
418
419
@Autowired
420
private MutableAclService mutableAclService;
421
422
@Autowired
423
private AclCache aclCache;
424
425
public MutableAcl updateAcl(MutableAcl acl) {
426
MutableAcl updated = mutableAclService.updateAcl(acl);
427
428
// Invalidate cache entry
429
aclCache.putInCache(updated);
430
431
// Also invalidate parent ACLs if inheritance is involved
432
if (updated.getParentAcl() != null) {
433
aclCache.evictFromCache(updated.getParentAcl().getObjectIdentity());
434
}
435
436
return updated;
437
}
438
}
439
```
440
441
### 3. Memory Management
442
443
Monitor and manage cache memory usage:
444
445
```java { .api }
446
@Configuration
447
public class AclCacheMemoryConfig {
448
449
@Bean
450
public CacheManager cacheManager() {
451
CaffeineCacheManager manager = new CaffeineCacheManager("aclCache");
452
manager.setCaffeine(Caffeine.newBuilder()
453
.maximumWeight(100_000_000) // 100MB
454
.weigher((key, value) -> {
455
// Custom weigher for ACL objects
456
if (value instanceof AclImpl) {
457
AclImpl acl = (AclImpl) value;
458
return 1000 + (acl.getEntries().size() * 100);
459
}
460
return 1000;
461
})
462
.recordStats());
463
return manager;
464
}
465
}
466
```
467
468
Proper caching and performance optimization is essential for production ACL deployments. The next step is understanding [configuration and setup](configuration.md) to integrate these performance features into your application.