0
# Configuration & Setup
1
2
This guide covers the complete setup of Spring Security ACL, including Spring configuration, database setup, and integration with Spring Security. Follow these steps to get ACL working in your application.
3
4
## Overview
5
6
Setting up Spring Security ACL involves:
7
8
1. **Dependencies** - Add required Spring Security modules
9
2. **Database Setup** - Create ACL tables and configure DataSource
10
3. **Spring Configuration** - Configure ACL services and security integration
11
4. **Method Security** - Enable annotation-based permission checking
12
5. **Testing** - Verify the setup works correctly
13
14
## Dependencies
15
16
### Maven Configuration
17
18
```xml { .api }
19
<properties>
20
<spring-security.version>6.5.1</spring-security.version>
21
<spring-boot.version>3.3.0</spring-boot.version>
22
</properties>
23
24
<dependencies>
25
<!-- Spring Security ACL -->
26
<dependency>
27
<groupId>org.springframework.security</groupId>
28
<artifactId>spring-security-acl</artifactId>
29
<version>${spring-security.version}</version>
30
</dependency>
31
32
<!-- Spring Security Config -->
33
<dependency>
34
<groupId>org.springframework.security</groupId>
35
<artifactId>spring-security-config</artifactId>
36
<version>${spring-security.version}</version>
37
</dependency>
38
39
<!-- Spring Boot Starter Security -->
40
<dependency>
41
<groupId>org.springframework.boot</groupId>
42
<artifactId>spring-boot-starter-security</artifactId>
43
</dependency>
44
45
<!-- Database dependencies (choose your database) -->
46
<dependency>
47
<groupId>org.springframework.boot</groupId>
48
<artifactId>spring-boot-starter-data-jpa</artifactId>
49
</dependency>
50
51
<!-- MySQL Driver -->
52
<dependency>
53
<groupId>mysql</groupId>
54
<artifactId>mysql-connector-java</artifactId>
55
<scope>runtime</scope>
56
</dependency>
57
58
<!-- Or PostgreSQL -->
59
<!--
60
<dependency>
61
<groupId>org.postgresql</groupId>
62
<artifactId>postgresql</artifactId>
63
<scope>runtime</scope>
64
</dependency>
65
-->
66
67
<!-- Connection Pool -->
68
<dependency>
69
<groupId>com.zaxxer</groupId>
70
<artifactId>HikariCP</artifactId>
71
</dependency>
72
73
<!-- Caching (optional but recommended) -->
74
<dependency>
75
<groupId>com.github.ben-manes.caffeine</groupId>
76
<artifactId>caffeine</artifactId>
77
</dependency>
78
</dependencies>
79
```
80
81
### Gradle Configuration
82
83
```groovy { .api }
84
dependencies {
85
implementation 'org.springframework.security:spring-security-acl:6.5.1'
86
implementation 'org.springframework.security:spring-security-config:6.5.1'
87
implementation 'org.springframework.boot:spring-boot-starter-security'
88
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
89
90
runtimeOnly 'mysql:mysql-connector-java'
91
// or: runtimeOnly 'org.postgresql:postgresql'
92
93
implementation 'com.zaxxer:HikariCP'
94
implementation 'com.github.ben-manes.caffeine:caffeine'
95
}
96
```
97
98
## Database Setup
99
100
### Schema Creation
101
102
The ACL module requires four tables. Here are the DDL statements for different databases:
103
104
#### MySQL Schema
105
106
```sql { .api }
107
-- ACL Class table - stores domain object types
108
CREATE TABLE acl_class (
109
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
110
class VARCHAR(100) NOT NULL,
111
UNIQUE KEY unique_uk_2 (class)
112
) ENGINE=InnoDB;
113
114
-- ACL SID table - stores security identities (users and roles)
115
CREATE TABLE acl_sid (
116
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
117
principal BOOLEAN NOT NULL,
118
sid VARCHAR(100) NOT NULL,
119
UNIQUE KEY unique_uk_3 (sid, principal)
120
) ENGINE=InnoDB;
121
122
-- ACL Object Identity table - stores domain object instances
123
CREATE TABLE acl_object_identity (
124
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
125
object_id_class BIGINT UNSIGNED NOT NULL,
126
object_id_identity VARCHAR(36) NOT NULL,
127
parent_object BIGINT UNSIGNED,
128
owner_sid BIGINT UNSIGNED,
129
entries_inheriting BOOLEAN NOT NULL,
130
UNIQUE KEY unique_uk_4 (object_id_class, object_id_identity),
131
CONSTRAINT foreign_fk_1 FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),
132
CONSTRAINT foreign_fk_2 FOREIGN KEY (object_id_class) REFERENCES acl_class (id),
133
CONSTRAINT foreign_fk_3 FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)
134
) ENGINE=InnoDB;
135
136
-- ACL Entry table - stores individual permission assignments
137
CREATE TABLE acl_entry (
138
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
139
acl_object_identity BIGINT UNSIGNED NOT NULL,
140
ace_order INTEGER NOT NULL,
141
sid BIGINT UNSIGNED NOT NULL,
142
mask INTEGER UNSIGNED NOT NULL,
143
granting BOOLEAN NOT NULL,
144
audit_success BOOLEAN NOT NULL,
145
audit_failure BOOLEAN NOT NULL,
146
UNIQUE KEY unique_uk_5 (acl_object_identity, ace_order),
147
CONSTRAINT foreign_fk_4 FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),
148
CONSTRAINT foreign_fk_5 FOREIGN KEY (sid) REFERENCES acl_sid (id)
149
) ENGINE=InnoDB;
150
151
-- Indexes for better performance
152
CREATE INDEX idx_acl_object_identity_parent ON acl_object_identity(parent_object);
153
CREATE INDEX idx_acl_entry_object_identity ON acl_entry(acl_object_identity);
154
CREATE INDEX idx_acl_entry_sid ON acl_entry(sid);
155
```
156
157
#### PostgreSQL Schema
158
159
```sql { .api }
160
-- ACL Class table
161
CREATE TABLE acl_class (
162
id BIGSERIAL NOT NULL PRIMARY KEY,
163
class VARCHAR(100) NOT NULL,
164
CONSTRAINT unique_uk_2 UNIQUE (class)
165
);
166
167
-- ACL SID table
168
CREATE TABLE acl_sid (
169
id BIGSERIAL NOT NULL PRIMARY KEY,
170
principal BOOLEAN NOT NULL,
171
sid VARCHAR(100) NOT NULL,
172
CONSTRAINT unique_uk_3 UNIQUE (sid, principal)
173
);
174
175
-- ACL Object Identity table
176
CREATE TABLE acl_object_identity (
177
id BIGSERIAL NOT NULL PRIMARY KEY,
178
object_id_class BIGINT NOT NULL,
179
object_id_identity VARCHAR(36) NOT NULL,
180
parent_object BIGINT,
181
owner_sid BIGINT,
182
entries_inheriting BOOLEAN NOT NULL,
183
CONSTRAINT unique_uk_4 UNIQUE (object_id_class, object_id_identity),
184
CONSTRAINT foreign_fk_1 FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),
185
CONSTRAINT foreign_fk_2 FOREIGN KEY (object_id_class) REFERENCES acl_class (id),
186
CONSTRAINT foreign_fk_3 FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)
187
);
188
189
-- ACL Entry table
190
CREATE TABLE acl_entry (
191
id BIGSERIAL NOT NULL PRIMARY KEY,
192
acl_object_identity BIGINT NOT NULL,
193
ace_order INTEGER NOT NULL,
194
sid BIGINT NOT NULL,
195
mask INTEGER NOT NULL,
196
granting BOOLEAN NOT NULL,
197
audit_success BOOLEAN NOT NULL,
198
audit_failure BOOLEAN NOT NULL,
199
CONSTRAINT unique_uk_5 UNIQUE (acl_object_identity, ace_order),
200
CONSTRAINT foreign_fk_4 FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),
201
CONSTRAINT foreign_fk_5 FOREIGN KEY (sid) REFERENCES acl_sid (id)
202
);
203
204
-- Indexes
205
CREATE INDEX idx_acl_object_identity_parent ON acl_object_identity(parent_object);
206
CREATE INDEX idx_acl_entry_object_identity ON acl_entry(acl_object_identity);
207
CREATE INDEX idx_acl_entry_sid ON acl_entry(sid);
208
```
209
210
### DataSource Configuration
211
212
#### Application Properties (MySQL)
213
214
```properties { .api }
215
# Database connection
216
spring.datasource.url=jdbc:mysql://localhost:3306/acl_database?useSSL=false&serverTimezone=UTC
217
spring.datasource.username=acl_user
218
spring.datasource.password=your_password
219
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
220
221
# Connection pool settings
222
spring.datasource.hikari.maximum-pool-size=20
223
spring.datasource.hikari.minimum-idle=5
224
spring.datasource.hikari.connection-timeout=30000
225
spring.datasource.hikari.idle-timeout=600000
226
spring.datasource.hikari.max-lifetime=1800000
227
228
# JPA settings
229
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
230
spring.jpa.show-sql=false
231
spring.jpa.hibernate.ddl-auto=validate
232
233
# ACL specific settings
234
logging.level.org.springframework.security.acls=DEBUG
235
```
236
237
#### DataSource Bean Configuration
238
239
```java { .api }
240
@Configuration
241
public class DataSourceConfig {
242
243
@Bean
244
@Primary
245
public DataSource dataSource() {
246
HikariConfig config = new HikariConfig();
247
config.setJdbcUrl("jdbc:mysql://localhost:3306/acl_database");
248
config.setUsername("acl_user");
249
config.setPassword("password");
250
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
251
252
// Performance tuning
253
config.setMaximumPoolSize(20);
254
config.setMinimumIdle(5);
255
config.setConnectionTimeout(30000);
256
config.setIdleTimeout(600000);
257
config.setMaxLifetime(1800000);
258
259
// ACL-specific optimizations
260
config.addDataSourceProperty("cachePrepStmts", "true");
261
config.addDataSourceProperty("prepStmtCacheSize", "250");
262
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
263
config.addDataSourceProperty("useServerPrepStmts", "true");
264
265
return new HikariDataSource(config);
266
}
267
}
268
```
269
270
## Spring Configuration
271
272
### Complete ACL Configuration
273
274
```java { .api }
275
@Configuration
276
@EnableGlobalMethodSecurity(prePostEnabled = true)
277
@EnableCaching
278
public class AclConfig {
279
280
@Autowired
281
private DataSource dataSource;
282
283
@Bean
284
public AclService aclService() {
285
JdbcMutableAclService service = new JdbcMutableAclService(
286
dataSource,
287
lookupStrategy(),
288
aclCache()
289
);
290
291
// Configure for your database
292
service.setClassIdentityQuery("SELECT @@IDENTITY"); // MySQL/SQL Server
293
service.setSidIdentityQuery("SELECT @@IDENTITY");
294
295
// For PostgreSQL use:
296
// service.setClassIdentityQuery("select currval(pg_get_serial_sequence('acl_class', 'id'))");
297
// service.setSidIdentityQuery("select currval(pg_get_serial_sequence('acl_sid', 'id'))");
298
299
return service;
300
}
301
302
@Bean
303
public MutableAclService mutableAclService() {
304
return (MutableAclService) aclService();
305
}
306
307
@Bean
308
public LookupStrategy lookupStrategy() {
309
return new BasicLookupStrategy(
310
dataSource,
311
aclCache(),
312
aclAuthorizationStrategy(),
313
permissionGrantingStrategy()
314
);
315
}
316
317
@Bean
318
public AclCache aclCache() {
319
return new SpringCacheBasedAclCache(
320
cacheManager().getCache("aclCache"),
321
permissionGrantingStrategy(),
322
aclAuthorizationStrategy()
323
);
324
}
325
326
@Bean
327
public PermissionGrantingStrategy permissionGrantingStrategy() {
328
return new DefaultPermissionGrantingStrategy(auditLogger());
329
}
330
331
@Bean
332
public AclAuthorizationStrategy aclAuthorizationStrategy() {
333
return new AclAuthorizationStrategyImpl(
334
new SimpleGrantedAuthority("ROLE_ADMIN"), // Change ownership
335
new SimpleGrantedAuthority("ROLE_ADMIN"), // Modify auditing
336
new SimpleGrantedAuthority("ROLE_ADMIN") // General changes
337
);
338
}
339
340
@Bean
341
public AuditLogger auditLogger() {
342
return new ConsoleAuditLogger();
343
}
344
345
@Bean
346
public PermissionFactory permissionFactory() {
347
DefaultPermissionFactory factory = new DefaultPermissionFactory();
348
349
// Register custom permissions if needed
350
factory.registerPublicPermissions(CustomPermission.class);
351
352
return factory;
353
}
354
355
@Bean
356
public CacheManager cacheManager() {
357
CaffeineCacheManager cacheManager = new CaffeineCacheManager("aclCache");
358
cacheManager.setCaffeine(Caffeine.newBuilder()
359
.maximumSize(10000)
360
.expireAfterWrite(Duration.ofMinutes(10))
361
.recordStats());
362
return cacheManager;
363
}
364
}
365
```
366
367
### Method Security Configuration
368
369
```java { .api }
370
@Configuration
371
@EnableGlobalMethodSecurity(prePostEnabled = true)
372
public class MethodSecurityConfig {
373
374
@Bean
375
public MethodSecurityExpressionHandler methodSecurityExpressionHandler(
376
AclService aclService,
377
PermissionFactory permissionFactory) {
378
379
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
380
381
// Configure permission evaluator
382
AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService);
383
permissionEvaluator.setPermissionFactory(permissionFactory);
384
permissionEvaluator.setObjectIdentityRetrievalStrategy(objectIdentityRetrievalStrategy());
385
permissionEvaluator.setSidRetrievalStrategy(sidRetrievalStrategy());
386
387
handler.setPermissionEvaluator(permissionEvaluator);
388
389
// Configure permission cache optimizer
390
handler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService));
391
392
return handler;
393
}
394
395
@Bean
396
public ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy() {
397
return new ObjectIdentityRetrievalStrategyImpl();
398
}
399
400
@Bean
401
public SidRetrievalStrategy sidRetrievalStrategy() {
402
return new SidRetrievalStrategyImpl();
403
}
404
}
405
```
406
407
## Spring Boot Auto-Configuration
408
409
For Spring Boot applications, you can create auto-configuration:
410
411
### ACL Auto-Configuration
412
413
```java { .api }
414
@Configuration
415
@ConditionalOnClass(AclService.class)
416
@ConditionalOnProperty(name = "spring.security.acl.enabled", havingValue = "true", matchIfMissing = true)
417
@EnableConfigurationProperties(AclProperties.class)
418
public class AclAutoConfiguration {
419
420
@Bean
421
@ConditionalOnMissingBean
422
public AclService aclService(
423
DataSource dataSource,
424
LookupStrategy lookupStrategy,
425
AclCache aclCache) {
426
427
JdbcMutableAclService service = new JdbcMutableAclService(
428
dataSource, lookupStrategy, aclCache
429
);
430
431
// Auto-detect database type and configure identity queries
432
configureDatabaseSpecificQueries(service, dataSource);
433
434
return service;
435
}
436
437
private void configureDatabaseSpecificQueries(JdbcMutableAclService service, DataSource dataSource) {
438
try (Connection conn = dataSource.getConnection()) {
439
String databaseName = conn.getMetaData().getDatabaseProductName().toLowerCase();
440
441
if (databaseName.contains("mysql")) {
442
service.setClassIdentityQuery("SELECT LAST_INSERT_ID()");
443
service.setSidIdentityQuery("SELECT LAST_INSERT_ID()");
444
} else if (databaseName.contains("postgresql")) {
445
service.setClassIdentityQuery("select currval(pg_get_serial_sequence('acl_class', 'id'))");
446
service.setSidIdentityQuery("select currval(pg_get_serial_sequence('acl_sid', 'id'))");
447
} else if (databaseName.contains("h2")) {
448
service.setClassIdentityQuery("SELECT SCOPE_IDENTITY()");
449
service.setSidIdentityQuery("SELECT SCOPE_IDENTITY()");
450
}
451
} catch (SQLException e) {
452
throw new IllegalStateException("Could not determine database type", e);
453
}
454
}
455
}
456
```
457
458
### Configuration Properties
459
460
```java { .api }
461
@ConfigurationProperties(prefix = "spring.security.acl")
462
public class AclProperties {
463
464
private boolean enabled = true;
465
private Cache cache = new Cache();
466
private Audit audit = new Audit();
467
468
// Getters and setters...
469
470
public static class Cache {
471
private String name = "aclCache";
472
private int maxSize = 10000;
473
private Duration expireAfterWrite = Duration.ofMinutes(10);
474
475
// Getters and setters...
476
}
477
478
public static class Audit {
479
private boolean enabled = true;
480
private String loggerType = "console"; // console, slf4j, custom
481
482
// Getters and setters...
483
}
484
}
485
```
486
487
## Security Integration
488
489
### Web Security Configuration
490
491
```java { .api }
492
@Configuration
493
@EnableWebSecurity
494
public class WebSecurityConfig {
495
496
@Bean
497
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
498
http
499
.authorizeHttpRequests(authz -> authz
500
.requestMatchers("/public/**").permitAll()
501
.requestMatchers("/admin/**").hasRole("ADMIN")
502
.anyRequest().authenticated()
503
)
504
.formLogin(form -> form
505
.loginPage("/login")
506
.defaultSuccessUrl("/dashboard")
507
.permitAll()
508
)
509
.logout(logout -> logout
510
.logoutUrl("/logout")
511
.logoutSuccessUrl("/login?logout")
512
.permitAll()
513
)
514
// Enable method security
515
.sessionManagement(session -> session
516
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
517
);
518
519
return http.build();
520
}
521
522
@Bean
523
public PasswordEncoder passwordEncoder() {
524
return new BCryptPasswordEncoder();
525
}
526
527
@Bean
528
public UserDetailsService userDetailsService() {
529
return new JdbcUserDetailsManager(dataSource());
530
}
531
}
532
```
533
534
### Custom Authentication Provider
535
536
```java { .api }
537
@Component
538
public class CustomAuthenticationProvider implements AuthenticationProvider {
539
540
@Autowired
541
private UserDetailsService userDetailsService;
542
543
@Autowired
544
private PasswordEncoder passwordEncoder;
545
546
@Override
547
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
548
String username = authentication.getName();
549
String password = authentication.getCredentials().toString();
550
551
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
552
553
if (passwordEncoder.matches(password, userDetails.getPassword())) {
554
return new UsernamePasswordAuthenticationToken(
555
userDetails, password, userDetails.getAuthorities()
556
);
557
}
558
559
throw new BadCredentialsException("Authentication failed");
560
}
561
562
@Override
563
public boolean supports(Class<?> authenticationType) {
564
return authenticationType.equals(UsernamePasswordAuthenticationToken.class);
565
}
566
}
567
```
568
569
## Testing Configuration
570
571
### Test Configuration
572
573
```java { .api }
574
@TestConfiguration
575
public class TestAclConfig {
576
577
@Bean
578
@Primary
579
public DataSource testDataSource() {
580
return new EmbeddedDatabaseBuilder()
581
.setType(EmbeddedDatabaseType.H2)
582
.addScript("classpath:acl-schema.sql")
583
.addScript("classpath:acl-test-data.sql")
584
.build();
585
}
586
587
@Bean
588
@Primary
589
public AclService testAclService() {
590
JdbcMutableAclService service = new JdbcMutableAclService(
591
testDataSource(), testLookupStrategy(), testAclCache()
592
);
593
594
// H2 specific queries
595
service.setClassIdentityQuery("SELECT SCOPE_IDENTITY()");
596
service.setSidIdentityQuery("SELECT SCOPE_IDENTITY()");
597
598
return service;
599
}
600
}
601
```
602
603
### Integration Test Example
604
605
```java { .api }
606
@SpringBootTest
607
@TestPropertySource(properties = {
608
"spring.datasource.url=jdbc:h2:mem:testdb",
609
"spring.jpa.hibernate.ddl-auto=create-drop"
610
})
611
@Sql(scripts = {
612
"classpath:acl-schema.sql",
613
"classpath:test-data.sql"
614
})
615
class AclIntegrationTest {
616
617
@Autowired
618
private MutableAclService aclService;
619
620
@Autowired
621
private DocumentService documentService;
622
623
@Test
624
@WithMockUser(username = "user1", roles = "USER")
625
void testDocumentAccessControl() {
626
// Create document with ACL
627
Document document = new Document("Test Document");
628
document = documentRepository.save(document);
629
630
ObjectIdentity identity = new ObjectIdentityImpl(Document.class, document.getId());
631
MutableAcl acl = aclService.createAcl(identity);
632
633
Sid userSid = new PrincipalSid("user1");
634
acl.insertAce(0, BasePermission.READ, userSid, true);
635
aclService.updateAcl(acl);
636
637
// Test access
638
Document result = documentService.getDocument(document.getId());
639
assertThat(result).isNotNull();
640
assertThat(result.getId()).isEqualTo(document.getId());
641
}
642
643
@Test
644
@WithMockUser(username = "user2", roles = "USER")
645
void testDocumentAccessDenied() {
646
// Document created for user1, accessed by user2
647
assertThatThrownBy(() -> documentService.getDocument(1L))
648
.isInstanceOf(AccessDeniedException.class);
649
}
650
}
651
```
652
653
## Production Considerations
654
655
### Performance Tuning
656
657
```java { .api }
658
@Configuration
659
public class ProductionAclConfig {
660
661
@Bean
662
public LookupStrategy optimizedLookupStrategy() {
663
BasicLookupStrategy strategy = new BasicLookupStrategy(
664
dataSource(),
665
aclCache(),
666
aclAuthorizationStrategy(),
667
permissionGrantingStrategy()
668
);
669
670
// Optimize batch size for bulk operations
671
strategy.setBatchSize(50);
672
673
return strategy;
674
}
675
676
@Bean
677
public CacheManager productionCacheManager() {
678
CaffeineCacheManager cacheManager = new CaffeineCacheManager("aclCache");
679
cacheManager.setCaffeine(Caffeine.newBuilder()
680
.maximumSize(100000) // Larger cache for production
681
.expireAfterWrite(Duration.ofHours(1)) // Longer expiry
682
.expireAfterAccess(Duration.ofMinutes(30))
683
.recordStats()
684
);
685
return cacheManager;
686
}
687
}
688
```
689
690
### Monitoring and Metrics
691
692
```java { .api }
693
@Configuration
694
public class AclMonitoringConfig {
695
696
@Bean
697
public CacheMetricsBinderConfiguration cacheMetrics() {
698
return new CacheMetricsBinderConfiguration();
699
}
700
701
@EventListener
702
public void handleAclCacheEvent(CacheEvictEvent event) {
703
if ("aclCache".equals(event.getCacheName())) {
704
// Log cache evictions for monitoring
705
log.info("ACL cache evicted for key: {}", event.getKey());
706
}
707
}
708
}
709
710
@Component
711
public class AclPerformanceMonitor {
712
713
private final MeterRegistry meterRegistry;
714
715
public AclPerformanceMonitor(MeterRegistry meterRegistry) {
716
this.meterRegistry = meterRegistry;
717
}
718
719
@EventListener
720
public void onAclLookup(AclLookupEvent event) {
721
Timer.Sample sample = Timer.start(meterRegistry);
722
// Record ACL lookup times
723
sample.stop(Timer.builder("acl.lookup.duration")
724
.tag("type", event.getType())
725
.register(meterRegistry));
726
}
727
}
728
```
729
730
### Security Hardening
731
732
```java { .api }
733
@Configuration
734
public class SecureAclConfig {
735
736
@Bean
737
public AclAuthorizationStrategy restrictiveAuthorizationStrategy() {
738
// Only ADMIN can modify ACLs
739
return new AclAuthorizationStrategyImpl(
740
new SimpleGrantedAuthority("ROLE_ADMIN"),
741
new SimpleGrantedAuthority("ROLE_ADMIN"),
742
new SimpleGrantedAuthority("ROLE_ADMIN")
743
);
744
}
745
746
@Bean
747
public AuditLogger secureAuditLogger() {
748
// Log to secure audit system
749
return new Slf4jAuditLogger();
750
}
751
752
// Custom audit logger for compliance
753
public static class Slf4jAuditLogger implements AuditLogger {
754
private static final Logger auditLog = LoggerFactory.getLogger("ACL_AUDIT");
755
756
@Override
757
public void logIfNeeded(boolean granted, AccessControlEntry ace) {
758
if (auditLog.isInfoEnabled()) {
759
auditLog.info("ACL Decision: granted={}, sid={}, permission={}, object={}",
760
granted, ace.getSid(), ace.getPermission(), ace.getAcl().getObjectIdentity());
761
}
762
}
763
}
764
}
765
```
766
767
## Troubleshooting
768
769
### Common Issues
770
771
#### Database Connection Issues
772
773
```java { .api }
774
// Problem: Connection pool exhaustion
775
// Solution: Tune connection pool settings
776
@Bean
777
public DataSource dataSource() {
778
HikariConfig config = new HikariConfig();
779
// ... other config
780
781
// Prevent connection leaks
782
config.setLeakDetectionThreshold(60000);
783
config.setConnectionTestQuery("SELECT 1");
784
785
return new HikariDataSource(config);
786
}
787
```
788
789
#### Performance Issues
790
791
```java { .api }
792
// Problem: N+1 queries during permission checks
793
// Solution: Use batch loading and caching
794
795
@Service
796
public class OptimizedDocumentService {
797
798
// Use @PostFilter with cache optimizer
799
@PostFilter("hasPermission(filterObject, 'READ')")
800
public List<Document> getDocuments() {
801
return documentRepository.findAll(); // Single query + batch ACL load
802
}
803
}
804
```
805
806
#### Permission Evaluation Errors
807
808
```java { .api }
809
// Problem: Null pointer exceptions in permission expressions
810
// Solution: Add null checks
811
812
@PreAuthorize("@securityService.canAccess(#document)")
813
public void updateDocument(Document document) {
814
// Custom security service handles null checks
815
}
816
817
@Service
818
public class SecurityService {
819
public boolean canAccess(Object object) {
820
if (object == null) return false;
821
// Permission check logic
822
return permissionEvaluator.hasPermission(authentication, object, "WRITE");
823
}
824
}
825
```
826
827
### Debug Configuration
828
829
```properties { .api }
830
# Enable ACL debug logging
831
logging.level.org.springframework.security.acls=DEBUG
832
logging.level.org.springframework.security.access=DEBUG
833
834
# Enable SQL logging to see ACL queries
835
logging.level.org.hibernate.SQL=DEBUG
836
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
837
838
# Enable cache logging
839
logging.level.org.springframework.cache=DEBUG
840
```
841
842
With this comprehensive configuration, your Spring Security ACL setup should be production-ready with proper performance, security, and monitoring capabilities. The modular configuration approach allows you to customize individual components based on your specific requirements.