or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

acl-services.mdcaching-performance.mdconfiguration.mddomain-model.mdindex.mdpermission-evaluation.mdstrategy-interfaces.md

configuration.mddocs/

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.