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

acl-services.mddocs/

0

# ACL Services

1

2

ACL Services provide the core functionality for reading and managing Access Control Lists. Spring Security ACL offers both read-only and mutable service interfaces, with production-ready JDBC implementations.

3

4

## Overview

5

6

The ACL module provides two main service interfaces:

7

8

- **`AclService`** - Read-only access to ACL data

9

- **`MutableAclService`** - Full CRUD operations on ACL data

10

11

Both are backed by efficient JDBC implementations that work with any SQL database.

12

13

## AclService Interface

14

15

The `AclService` provides read-only access to ACL data with methods optimized for different use cases:

16

17

```java { .api }

18

package org.springframework.security.acls.model;

19

20

public interface AclService {

21

22

// Find child object identities

23

List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity);

24

25

// Read single ACL (not recommended - doesn't allow SID filtering)

26

Acl readAclById(ObjectIdentity object) throws NotFoundException;

27

28

// Read single ACL with SID filtering (recommended)

29

Acl readAclById(ObjectIdentity object, List<Sid> sids) throws NotFoundException;

30

31

// Batch read ACLs

32

Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects) throws NotFoundException;

33

34

// Batch read ACLs with SID filtering (most efficient)

35

Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids)

36

throws NotFoundException;

37

}

38

```

39

40

### Key Methods

41

42

**Single ACL Retrieval:**

43

```java { .api }

44

@Autowired

45

private AclService aclService;

46

47

public boolean checkDocumentAccess(Long documentId, Authentication auth) {

48

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, documentId);

49

List<Sid> sids = sidRetrievalStrategy.getSids(auth);

50

51

try {

52

// Load ACL with SID filtering for better performance

53

Acl acl = aclService.readAclById(identity, sids);

54

return acl.isGranted(Arrays.asList(BasePermission.READ), sids, false);

55

} catch (NotFoundException e) {

56

return false; // No ACL exists

57

}

58

}

59

```

60

61

**Batch ACL Retrieval:**

62

```java { .api }

63

public Map<Document, Boolean> checkMultipleDocuments(List<Document> documents, Authentication auth) {

64

// Create object identities

65

List<ObjectIdentity> identities = documents.stream()

66

.map(doc -> new ObjectIdentityImpl(Document.class, doc.getId()))

67

.collect(Collectors.toList());

68

69

List<Sid> sids = sidRetrievalStrategy.getSids(auth);

70

List<Permission> readPermission = Arrays.asList(BasePermission.READ);

71

72

try {

73

// Batch load ACLs - much more efficient than individual calls

74

Map<ObjectIdentity, Acl> acls = aclService.readAclsById(identities, sids);

75

76

return documents.stream()

77

.collect(Collectors.toMap(

78

doc -> doc,

79

doc -> {

80

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, doc.getId());

81

Acl acl = acls.get(identity);

82

return acl != null && acl.isGranted(readPermission, sids, false);

83

}

84

));

85

} catch (NotFoundException e) {

86

// Return all false if any ACL is missing

87

return documents.stream()

88

.collect(Collectors.toMap(doc -> doc, doc -> false));

89

}

90

}

91

```

92

93

**Finding Child Objects:**

94

```java { .api }

95

public List<ObjectIdentity> getFolderContents(Long folderId) {

96

ObjectIdentity folderIdentity = new ObjectIdentityImpl(Folder.class, folderId);

97

return aclService.findChildren(folderIdentity);

98

}

99

```

100

101

## MutableAclService Interface

102

103

The `MutableAclService` extends `AclService` with methods for creating, modifying, and deleting ACLs:

104

105

```java { .api }

106

package org.springframework.security.acls.model;

107

108

public interface MutableAclService extends AclService {

109

110

// Create new ACL

111

MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException;

112

113

// Delete ACL

114

void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren)

115

throws ChildrenExistException;

116

117

// Update existing ACL

118

MutableAcl updateAcl(MutableAcl acl) throws NotFoundException;

119

}

120

```

121

122

### Creating ACLs

123

124

```java { .api }

125

@Autowired

126

private MutableAclService mutableAclService;

127

128

public void createDocumentWithPermissions(Document document, String owner) {

129

// Save document first

130

document = documentRepository.save(document);

131

132

// Create object identity

133

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, document.getId());

134

135

try {

136

// Create ACL

137

MutableAcl acl = mutableAclService.createAcl(identity);

138

139

// Set owner

140

Sid ownerSid = new PrincipalSid(owner);

141

acl.setOwner(ownerSid);

142

143

// Grant owner full permissions

144

acl.insertAce(0, BasePermission.ADMINISTRATION, ownerSid, true);

145

acl.insertAce(1, BasePermission.DELETE, ownerSid, true);

146

acl.insertAce(2, BasePermission.WRITE, ownerSid, true);

147

acl.insertAce(3, BasePermission.READ, ownerSid, true);

148

149

// Grant read permission to all authenticated users

150

Sid userRole = new GrantedAuthoritySid("ROLE_USER");

151

acl.insertAce(4, BasePermission.READ, userRole, true);

152

153

// Save ACL

154

mutableAclService.updateAcl(acl);

155

156

} catch (AlreadyExistsException e) {

157

throw new IllegalStateException("ACL already exists for document: " + document.getId());

158

}

159

}

160

```

161

162

### Updating ACL Permissions

163

164

```java { .api }

165

public void grantUserPermission(Long documentId, String username, Permission permission) {

166

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, documentId);

167

168

// Load existing ACL as mutable

169

MutableAcl acl = (MutableAcl) mutableAclService.readAclById(identity);

170

171

// Add new permission

172

Sid userSid = new PrincipalSid(username);

173

acl.insertAce(acl.getEntries().size(), permission, userSid, true);

174

175

// Update ACL

176

mutableAclService.updateAcl(acl);

177

}

178

179

public void revokeUserPermission(Long documentId, String username, Permission permission) {

180

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, documentId);

181

MutableAcl acl = (MutableAcl) mutableAclService.readAclById(identity);

182

183

Sid userSid = new PrincipalSid(username);

184

185

// Find and remove matching ACE

186

List<AccessControlEntry> entries = acl.getEntries();

187

for (int i = 0; i < entries.size(); i++) {

188

AccessControlEntry ace = entries.get(i);

189

if (ace.getSid().equals(userSid) && ace.getPermission().equals(permission)) {

190

acl.deleteAce(i);

191

break;

192

}

193

}

194

195

mutableAclService.updateAcl(acl);

196

}

197

```

198

199

### Deleting ACLs

200

201

```java { .api }

202

public void deleteDocument(Long documentId, boolean deleteChildACLs) {

203

// Delete domain object first

204

documentRepository.deleteById(documentId);

205

206

// Delete ACL

207

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, documentId);

208

209

try {

210

mutableAclService.deleteAcl(identity, deleteChildACLs);

211

} catch (ChildrenExistException e) {

212

throw new IllegalStateException(

213

"Cannot delete ACL - child ACLs exist. Use deleteChildACLs=true or delete children first.");

214

}

215

}

216

```

217

218

## JDBC Implementations

219

220

### JdbcAclService

221

222

The production-ready read-only implementation:

223

224

```java { .api }

225

@Configuration

226

public class AclServiceConfig {

227

228

@Bean

229

public AclService aclService(DataSource dataSource) {

230

JdbcAclService service = new JdbcAclService(dataSource, lookupStrategy());

231

return service;

232

}

233

234

@Bean

235

public LookupStrategy lookupStrategy() {

236

return new BasicLookupStrategy(

237

dataSource(),

238

aclCache(),

239

aclAuthorizationStrategy(),

240

auditLogger()

241

);

242

}

243

}

244

```

245

246

### JdbcMutableAclService

247

248

The full-featured implementation with create/update/delete capabilities:

249

250

```java { .api }

251

@Configuration

252

public class MutableAclServiceConfig {

253

254

@Bean

255

public MutableAclService mutableAclService(DataSource dataSource) {

256

JdbcMutableAclService service = new JdbcMutableAclService(

257

dataSource,

258

lookupStrategy(),

259

aclCache()

260

);

261

262

// Configure for your database

263

service.setClassIdentityQuery("SELECT @@IDENTITY"); // SQL Server

264

service.setSidIdentityQuery("SELECT @@IDENTITY");

265

266

// For MySQL:

267

// service.setClassIdentityQuery("SELECT LAST_INSERT_ID()");

268

// service.setSidIdentityQuery("SELECT LAST_INSERT_ID()");

269

270

// For PostgreSQL:

271

// service.setClassIdentityQuery("select currval(pg_get_serial_sequence('acl_class', 'id'))");

272

// service.setSidIdentityQuery("select currval(pg_get_serial_sequence('acl_sid', 'id'))");

273

274

return service;

275

}

276

}

277

```

278

279

### Database Configuration

280

281

The JDBC implementations require specific database tables:

282

283

```sql { .api }

284

-- ACL Class table

285

CREATE TABLE acl_class (

286

id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,

287

class VARCHAR(100) NOT NULL,

288

UNIQUE KEY unique_uk_2 (class)

289

);

290

291

-- ACL SID table

292

CREATE TABLE acl_sid (

293

id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,

294

principal BOOLEAN NOT NULL,

295

sid VARCHAR(100) NOT NULL,

296

UNIQUE KEY unique_uk_3 (sid, principal)

297

);

298

299

-- ACL Object Identity table

300

CREATE TABLE acl_object_identity (

301

id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,

302

object_id_class BIGINT UNSIGNED NOT NULL,

303

object_id_identity VARCHAR(36) NOT NULL,

304

parent_object BIGINT UNSIGNED,

305

owner_sid BIGINT UNSIGNED,

306

entries_inheriting BOOLEAN NOT NULL,

307

UNIQUE KEY unique_uk_4 (object_id_class, object_id_identity),

308

CONSTRAINT foreign_fk_1 FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),

309

CONSTRAINT foreign_fk_2 FOREIGN KEY (object_id_class) REFERENCES acl_class (id),

310

CONSTRAINT foreign_fk_3 FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)

311

);

312

313

-- ACL Entry table

314

CREATE TABLE acl_entry (

315

id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,

316

acl_object_identity BIGINT UNSIGNED NOT NULL,

317

ace_order INTEGER NOT NULL,

318

sid BIGINT UNSIGNED NOT NULL,

319

mask INTEGER UNSIGNED NOT NULL,

320

granting BOOLEAN NOT NULL,

321

audit_success BOOLEAN NOT NULL,

322

audit_failure BOOLEAN NOT NULL,

323

UNIQUE KEY unique_uk_5 (acl_object_identity, ace_order),

324

CONSTRAINT foreign_fk_4 FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),

325

CONSTRAINT foreign_fk_5 FOREIGN KEY (sid) REFERENCES acl_sid (id)

326

);

327

```

328

329

## Advanced Features

330

331

### Lookup Strategy

332

333

The `LookupStrategy` interface allows customization of how ACLs are retrieved:

334

335

```java { .api }

336

public interface LookupStrategy {

337

Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids);

338

}

339

340

// Basic implementation for ANSI SQL databases

341

@Bean

342

public LookupStrategy lookupStrategy() {

343

BasicLookupStrategy strategy = new BasicLookupStrategy(

344

dataSource(),

345

aclCache(),

346

aclAuthorizationStrategy(),

347

permissionGrantingStrategy()

348

);

349

350

// Customize SQL queries if needed

351

strategy.setSelectClause("SELECT obj.object_id_identity, class.class, sid.sid, sid.principal, " +

352

"acl.entries_inheriting, acl.id as acl_id, acl.parent_object, acl.owner_sid, " +

353

"entry.id, entry.mask, entry.granting, entry.audit_success, entry.audit_failure ");

354

355

return strategy;

356

}

357

```

358

359

### Caching

360

361

ACL data is cached to improve performance:

362

363

```java { .api }

364

@Bean

365

public AclCache aclCache() {

366

// Use Spring's caching abstraction

367

return new SpringCacheBasedAclCache(

368

cacheManager().getCache("aclCache"),

369

permissionGrantingStrategy(),

370

aclAuthorizationStrategy()

371

);

372

}

373

374

@Bean

375

public CacheManager cacheManager() {

376

ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager("aclCache");

377

return cacheManager;

378

}

379

```

380

381

### Batch Processing

382

383

For better performance when working with many objects:

384

385

```java { .api }

386

@Service

387

public class DocumentSecurityService {

388

389

@Autowired

390

private MutableAclService aclService;

391

392

@Transactional

393

public void createDocumentsWithBulkPermissions(List<Document> documents, String owner) {

394

// Save all documents first

395

documents = documentRepository.saveAll(documents);

396

397

// Create ACLs in batch

398

for (Document doc : documents) {

399

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, doc.getId());

400

401

MutableAcl acl = aclService.createAcl(identity);

402

setupDefaultPermissions(acl, owner);

403

aclService.updateAcl(acl);

404

}

405

}

406

407

private void setupDefaultPermissions(MutableAcl acl, String owner) {

408

Sid ownerSid = new PrincipalSid(owner);

409

acl.setOwner(ownerSid);

410

acl.insertAce(0, BasePermission.ADMINISTRATION, ownerSid, true);

411

412

Sid userRole = new GrantedAuthoritySid("ROLE_USER");

413

acl.insertAce(1, BasePermission.READ, userRole, true);

414

}

415

}

416

```

417

418

## Performance Optimization

419

420

### 1. Use SID Filtering

421

422

Always provide SID lists when possible to reduce data transfer:

423

424

```java { .api }

425

// Less efficient - loads all ACL entries

426

Acl acl = aclService.readAclById(objectIdentity);

427

428

// More efficient - only loads relevant entries

429

List<Sid> sids = sidRetrievalStrategy.getSids(authentication);

430

Acl acl = aclService.readAclById(objectIdentity, sids);

431

```

432

433

### 2. Batch Operations

434

435

Load multiple ACLs in single call:

436

437

```java { .api }

438

// Less efficient - multiple database queries

439

List<Boolean> results = new ArrayList<>();

440

for (ObjectIdentity identity : identities) {

441

Acl acl = aclService.readAclById(identity, sids);

442

results.add(acl.isGranted(permissions, sids, false));

443

}

444

445

// More efficient - single batch query

446

Map<ObjectIdentity, Acl> acls = aclService.readAclsById(identities, sids);

447

List<Boolean> results = identities.stream()

448

.map(identity -> {

449

Acl acl = acls.get(identity);

450

return acl != null && acl.isGranted(permissions, sids, false);

451

})

452

.collect(Collectors.toList());

453

```

454

455

### 3. Cache Configuration

456

457

```java { .api }

458

@Configuration

459

@EnableCaching

460

public class CacheConfig {

461

462

@Bean

463

public CacheManager cacheManager() {

464

CaffeineCacheManager cacheManager = new CaffeineCacheManager("aclCache");

465

cacheManager.setCaffeine(Caffeine.newBuilder()

466

.maximumSize(10000)

467

.expireAfterWrite(Duration.ofMinutes(10))

468

.recordStats());

469

return cacheManager;

470

}

471

}

472

```

473

474

### 4. Connection Pooling

475

476

```java { .api }

477

@Configuration

478

public class DataSourceConfig {

479

480

@Bean

481

@Primary

482

public DataSource dataSource() {

483

HikariConfig config = new HikariConfig();

484

config.setJdbcUrl("jdbc:mysql://localhost/acl_db");

485

config.setUsername("user");

486

config.setPassword("password");

487

488

// Optimize for ACL queries

489

config.setMaximumPoolSize(20);

490

config.setMinimumIdle(5);

491

config.setConnectionTimeout(30000);

492

config.setIdleTimeout(600000);

493

494

return new HikariDataSource(config);

495

}

496

}

497

```

498

499

## Exception Handling

500

501

Handle ACL-specific exceptions appropriately:

502

503

```java { .api }

504

@Service

505

public class SecureDocumentService {

506

507

public Optional<Document> getDocumentIfAllowed(Long documentId, Authentication auth) {

508

try {

509

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, documentId);

510

List<Sid> sids = sidRetrievalStrategy.getSids(auth);

511

512

Acl acl = aclService.readAclById(identity, sids);

513

514

if (acl.isGranted(Arrays.asList(BasePermission.READ), sids, false)) {

515

return documentRepository.findById(documentId);

516

}

517

return Optional.empty();

518

519

} catch (NotFoundException e) {

520

log.debug("No ACL found for document {}, denying access", documentId);

521

return Optional.empty();

522

523

} catch (UnloadedSidException e) {

524

log.warn("ACL loaded without required SIDs for document {}", documentId);

525

// Retry with full SID loading or return empty

526

return Optional.empty();

527

}

528

}

529

}

530

```

531

532

## Best Practices

533

534

### 1. Use Appropriate Service Level

535

```java { .api }

536

// For read-only operations, inject AclService

537

@Autowired

538

private AclService aclService;

539

540

// For modifications, inject MutableAclService

541

@Autowired

542

private MutableAclService mutableAclService;

543

```

544

545

### 2. Handle ACL Lifecycle with Domain Objects

546

```java { .api }

547

@Entity

548

@EntityListeners(DocumentAclListener.class)

549

public class Document {

550

// Entity fields...

551

}

552

553

@Component

554

public class DocumentAclListener {

555

556

@Autowired

557

private MutableAclService aclService;

558

559

@PostPersist

560

public void createAcl(Document document) {

561

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, document.getId());

562

// Create ACL with default permissions

563

}

564

565

@PostRemove

566

public void deleteAcl(Document document) {

567

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, document.getId());

568

aclService.deleteAcl(identity, true);

569

}

570

}

571

```

572

573

### 3. Transactional Boundaries

574

```java { .api }

575

@Transactional

576

public void createDocumentWithPermissions(Document document) {

577

// Both domain object and ACL operations should be in same transaction

578

document = documentRepository.save(document);

579

580

ObjectIdentity identity = new ObjectIdentityImpl(Document.class, document.getId());

581

MutableAcl acl = mutableAclService.createAcl(identity);

582

// Configure ACL...

583

mutableAclService.updateAcl(acl);

584

}

585

```

586

587

The ACL services provide a solid foundation for managing permissions at scale. The next step is understanding how to integrate these services with [Spring Security's permission evaluation](permission-evaluation.md) for a modern annotation-based approach.