CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-security--spring-security-acl

Spring Security ACL provides instance-based security for domain objects through a comprehensive Access Control List implementation

Pending
Overview
Eval results
Files

configuration.mddocs/

Configuration & Setup

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.

Overview

Setting up Spring Security ACL involves:

  1. Dependencies - Add required Spring Security modules
  2. Database Setup - Create ACL tables and configure DataSource
  3. Spring Configuration - Configure ACL services and security integration
  4. Method Security - Enable annotation-based permission checking
  5. Testing - Verify the setup works correctly

Dependencies

Maven Configuration

<properties>
    <spring-security.version>6.5.1</spring-security.version>
    <spring-boot.version>3.3.0</spring-boot.version>
</properties>

<dependencies>
    <!-- Spring Security ACL -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-acl</artifactId>
        <version>${spring-security.version}</version>
    </dependency>
    
    <!-- Spring Security Config -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>${spring-security.version}</version>
    </dependency>
    
    <!-- Spring Boot Starter Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
    <!-- Database dependencies (choose your database) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- MySQL Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Or PostgreSQL -->
    <!--
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
    -->
    
    <!-- Connection Pool -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
    </dependency>
    
    <!-- Caching (optional but recommended) -->
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
</dependencies>

Gradle Configuration

dependencies {
    implementation 'org.springframework.security:spring-security-acl:6.5.1'
    implementation 'org.springframework.security:spring-security-config:6.5.1'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    
    runtimeOnly 'mysql:mysql-connector-java'
    // or: runtimeOnly 'org.postgresql:postgresql'
    
    implementation 'com.zaxxer:HikariCP'
    implementation 'com.github.ben-manes.caffeine:caffeine'
}

Database Setup

Schema Creation

The ACL module requires four tables. Here are the DDL statements for different databases:

MySQL Schema

-- ACL Class table - stores domain object types
CREATE TABLE acl_class (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    class VARCHAR(100) NOT NULL,
    UNIQUE KEY unique_uk_2 (class)
) ENGINE=InnoDB;

-- ACL SID table - stores security identities (users and roles)
CREATE TABLE acl_sid (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    principal BOOLEAN NOT NULL,
    sid VARCHAR(100) NOT NULL,
    UNIQUE KEY unique_uk_3 (sid, principal)
) ENGINE=InnoDB;

-- ACL Object Identity table - stores domain object instances
CREATE TABLE acl_object_identity (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    object_id_class BIGINT UNSIGNED NOT NULL,
    object_id_identity VARCHAR(36) NOT NULL,
    parent_object BIGINT UNSIGNED,
    owner_sid BIGINT UNSIGNED,
    entries_inheriting BOOLEAN NOT NULL,
    UNIQUE KEY unique_uk_4 (object_id_class, object_id_identity),
    CONSTRAINT foreign_fk_1 FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),
    CONSTRAINT foreign_fk_2 FOREIGN KEY (object_id_class) REFERENCES acl_class (id),
    CONSTRAINT foreign_fk_3 FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)
) ENGINE=InnoDB;

-- ACL Entry table - stores individual permission assignments  
CREATE TABLE acl_entry (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    acl_object_identity BIGINT UNSIGNED NOT NULL,
    ace_order INTEGER NOT NULL,
    sid BIGINT UNSIGNED NOT NULL,
    mask INTEGER UNSIGNED NOT NULL,
    granting BOOLEAN NOT NULL,
    audit_success BOOLEAN NOT NULL,
    audit_failure BOOLEAN NOT NULL,
    UNIQUE KEY unique_uk_5 (acl_object_identity, ace_order),
    CONSTRAINT foreign_fk_4 FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),
    CONSTRAINT foreign_fk_5 FOREIGN KEY (sid) REFERENCES acl_sid (id)
) ENGINE=InnoDB;

-- Indexes for better performance
CREATE INDEX idx_acl_object_identity_parent ON acl_object_identity(parent_object);
CREATE INDEX idx_acl_entry_object_identity ON acl_entry(acl_object_identity);
CREATE INDEX idx_acl_entry_sid ON acl_entry(sid);

PostgreSQL Schema

-- ACL Class table
CREATE TABLE acl_class (
    id BIGSERIAL NOT NULL PRIMARY KEY,
    class VARCHAR(100) NOT NULL,
    CONSTRAINT unique_uk_2 UNIQUE (class)
);

-- ACL SID table
CREATE TABLE acl_sid (
    id BIGSERIAL NOT NULL PRIMARY KEY,
    principal BOOLEAN NOT NULL,
    sid VARCHAR(100) NOT NULL,
    CONSTRAINT unique_uk_3 UNIQUE (sid, principal)
);

-- ACL Object Identity table
CREATE TABLE acl_object_identity (
    id BIGSERIAL NOT NULL PRIMARY KEY,
    object_id_class BIGINT NOT NULL,
    object_id_identity VARCHAR(36) NOT NULL,
    parent_object BIGINT,
    owner_sid BIGINT,
    entries_inheriting BOOLEAN NOT NULL,
    CONSTRAINT unique_uk_4 UNIQUE (object_id_class, object_id_identity),
    CONSTRAINT foreign_fk_1 FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),
    CONSTRAINT foreign_fk_2 FOREIGN KEY (object_id_class) REFERENCES acl_class (id),
    CONSTRAINT foreign_fk_3 FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)
);

-- ACL Entry table
CREATE TABLE acl_entry (
    id BIGSERIAL NOT NULL PRIMARY KEY,
    acl_object_identity BIGINT NOT NULL,
    ace_order INTEGER NOT NULL,
    sid BIGINT NOT NULL,
    mask INTEGER NOT NULL,
    granting BOOLEAN NOT NULL,
    audit_success BOOLEAN NOT NULL,
    audit_failure BOOLEAN NOT NULL,
    CONSTRAINT unique_uk_5 UNIQUE (acl_object_identity, ace_order),
    CONSTRAINT foreign_fk_4 FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),
    CONSTRAINT foreign_fk_5 FOREIGN KEY (sid) REFERENCES acl_sid (id)
);

-- Indexes
CREATE INDEX idx_acl_object_identity_parent ON acl_object_identity(parent_object);
CREATE INDEX idx_acl_entry_object_identity ON acl_entry(acl_object_identity);
CREATE INDEX idx_acl_entry_sid ON acl_entry(sid);

DataSource Configuration

Application Properties (MySQL)

# Database connection
spring.datasource.url=jdbc:mysql://localhost:3306/acl_database?useSSL=false&serverTimezone=UTC
spring.datasource.username=acl_user
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Connection pool settings
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000

# JPA settings
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=validate

# ACL specific settings
logging.level.org.springframework.security.acls=DEBUG

DataSource Bean Configuration

@Configuration
public class DataSourceConfig {
    
    @Bean
    @Primary
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/acl_database");
        config.setUsername("acl_user");
        config.setPassword("password");
        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
        
        // Performance tuning
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        config.setConnectionTimeout(30000);
        config.setIdleTimeout(600000);
        config.setMaxLifetime(1800000);
        
        // ACL-specific optimizations
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        config.addDataSourceProperty("useServerPrepStmts", "true");
        
        return new HikariDataSource(config);
    }
}

Spring Configuration

Complete ACL Configuration

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableCaching
public class AclConfig {
    
    @Autowired
    private DataSource dataSource;
    
    @Bean
    public AclService aclService() {
        JdbcMutableAclService service = new JdbcMutableAclService(
            dataSource, 
            lookupStrategy(), 
            aclCache()
        );
        
        // Configure for your database
        service.setClassIdentityQuery("SELECT @@IDENTITY");        // MySQL/SQL Server
        service.setSidIdentityQuery("SELECT @@IDENTITY");
        
        // For PostgreSQL use:
        // service.setClassIdentityQuery("select currval(pg_get_serial_sequence('acl_class', 'id'))");
        // service.setSidIdentityQuery("select currval(pg_get_serial_sequence('acl_sid', 'id'))");
        
        return service;
    }
    
    @Bean
    public MutableAclService mutableAclService() {
        return (MutableAclService) aclService();
    }
    
    @Bean
    public LookupStrategy lookupStrategy() {
        return new BasicLookupStrategy(
            dataSource,
            aclCache(),
            aclAuthorizationStrategy(),
            permissionGrantingStrategy()
        );
    }
    
    @Bean
    public AclCache aclCache() {
        return new SpringCacheBasedAclCache(
            cacheManager().getCache("aclCache"),
            permissionGrantingStrategy(),
            aclAuthorizationStrategy()
        );
    }
    
    @Bean
    public PermissionGrantingStrategy permissionGrantingStrategy() {
        return new DefaultPermissionGrantingStrategy(auditLogger());
    }
    
    @Bean
    public AclAuthorizationStrategy aclAuthorizationStrategy() {
        return new AclAuthorizationStrategyImpl(
            new SimpleGrantedAuthority("ROLE_ADMIN"),    // Change ownership
            new SimpleGrantedAuthority("ROLE_ADMIN"),    // Modify auditing  
            new SimpleGrantedAuthority("ROLE_ADMIN")     // General changes
        );
    }
    
    @Bean
    public AuditLogger auditLogger() {
        return new ConsoleAuditLogger();
    }
    
    @Bean
    public PermissionFactory permissionFactory() {
        DefaultPermissionFactory factory = new DefaultPermissionFactory();
        
        // Register custom permissions if needed
        factory.registerPublicPermissions(CustomPermission.class);
        
        return factory;
    }
    
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("aclCache");
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(10000)
            .expireAfterWrite(Duration.ofMinutes(10))
            .recordStats());
        return cacheManager;
    }
}

Method Security Configuration

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
    
    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler(
            AclService aclService,
            PermissionFactory permissionFactory) {
        
        DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
        
        // Configure permission evaluator
        AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService);
        permissionEvaluator.setPermissionFactory(permissionFactory);
        permissionEvaluator.setObjectIdentityRetrievalStrategy(objectIdentityRetrievalStrategy());
        permissionEvaluator.setSidRetrievalStrategy(sidRetrievalStrategy());
        
        handler.setPermissionEvaluator(permissionEvaluator);
        
        // Configure permission cache optimizer
        handler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService));
        
        return handler;
    }
    
    @Bean
    public ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy() {
        return new ObjectIdentityRetrievalStrategyImpl();
    }
    
    @Bean
    public SidRetrievalStrategy sidRetrievalStrategy() {
        return new SidRetrievalStrategyImpl();
    }
}

Spring Boot Auto-Configuration

For Spring Boot applications, you can create auto-configuration:

ACL Auto-Configuration

@Configuration
@ConditionalOnClass(AclService.class)
@ConditionalOnProperty(name = "spring.security.acl.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(AclProperties.class)
public class AclAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public AclService aclService(
            DataSource dataSource,
            LookupStrategy lookupStrategy,
            AclCache aclCache) {
        
        JdbcMutableAclService service = new JdbcMutableAclService(
            dataSource, lookupStrategy, aclCache
        );
        
        // Auto-detect database type and configure identity queries
        configureDatabaseSpecificQueries(service, dataSource);
        
        return service;
    }
    
    private void configureDatabaseSpecificQueries(JdbcMutableAclService service, DataSource dataSource) {
        try (Connection conn = dataSource.getConnection()) {
            String databaseName = conn.getMetaData().getDatabaseProductName().toLowerCase();
            
            if (databaseName.contains("mysql")) {
                service.setClassIdentityQuery("SELECT LAST_INSERT_ID()");
                service.setSidIdentityQuery("SELECT LAST_INSERT_ID()");
            } else if (databaseName.contains("postgresql")) {
                service.setClassIdentityQuery("select currval(pg_get_serial_sequence('acl_class', 'id'))");
                service.setSidIdentityQuery("select currval(pg_get_serial_sequence('acl_sid', 'id'))");
            } else if (databaseName.contains("h2")) {
                service.setClassIdentityQuery("SELECT SCOPE_IDENTITY()");
                service.setSidIdentityQuery("SELECT SCOPE_IDENTITY()");
            }
        } catch (SQLException e) {
            throw new IllegalStateException("Could not determine database type", e);
        }
    }
}

Configuration Properties

@ConfigurationProperties(prefix = "spring.security.acl")
public class AclProperties {
    
    private boolean enabled = true;
    private Cache cache = new Cache();
    private Audit audit = new Audit();
    
    // Getters and setters...
    
    public static class Cache {
        private String name = "aclCache";
        private int maxSize = 10000;
        private Duration expireAfterWrite = Duration.ofMinutes(10);
        
        // Getters and setters...
    }
    
    public static class Audit {
        private boolean enabled = true;
        private String loggerType = "console"; // console, slf4j, custom
        
        // Getters and setters...
    }
}

Security Integration

Web Security Configuration

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout")
                .permitAll()
            )
            // Enable method security
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            );
            
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public UserDetailsService userDetailsService() {
        return new JdbcUserDetailsManager(dataSource());
    }
}

Custom Authentication Provider

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        
        if (passwordEncoder.matches(password, userDetails.getPassword())) {
            return new UsernamePasswordAuthenticationToken(
                userDetails, password, userDetails.getAuthorities()
            );
        }
        
        throw new BadCredentialsException("Authentication failed");
    }
    
    @Override
    public boolean supports(Class<?> authenticationType) {
        return authenticationType.equals(UsernamePasswordAuthenticationToken.class);
    }
}

Testing Configuration

Test Configuration

@TestConfiguration
public class TestAclConfig {
    
    @Bean
    @Primary
    public DataSource testDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:acl-schema.sql")
            .addScript("classpath:acl-test-data.sql")
            .build();
    }
    
    @Bean
    @Primary 
    public AclService testAclService() {
        JdbcMutableAclService service = new JdbcMutableAclService(
            testDataSource(), testLookupStrategy(), testAclCache()
        );
        
        // H2 specific queries
        service.setClassIdentityQuery("SELECT SCOPE_IDENTITY()");
        service.setSidIdentityQuery("SELECT SCOPE_IDENTITY()");
        
        return service;
    }
}

Integration Test Example

@SpringBootTest
@TestPropertySource(properties = {
    "spring.datasource.url=jdbc:h2:mem:testdb",
    "spring.jpa.hibernate.ddl-auto=create-drop"
})
@Sql(scripts = {
    "classpath:acl-schema.sql",
    "classpath:test-data.sql"
})
class AclIntegrationTest {
    
    @Autowired
    private MutableAclService aclService;
    
    @Autowired
    private DocumentService documentService;
    
    @Test
    @WithMockUser(username = "user1", roles = "USER")
    void testDocumentAccessControl() {
        // Create document with ACL
        Document document = new Document("Test Document");
        document = documentRepository.save(document);
        
        ObjectIdentity identity = new ObjectIdentityImpl(Document.class, document.getId());
        MutableAcl acl = aclService.createAcl(identity);
        
        Sid userSid = new PrincipalSid("user1");
        acl.insertAce(0, BasePermission.READ, userSid, true);
        aclService.updateAcl(acl);
        
        // Test access
        Document result = documentService.getDocument(document.getId());
        assertThat(result).isNotNull();
        assertThat(result.getId()).isEqualTo(document.getId());
    }
    
    @Test
    @WithMockUser(username = "user2", roles = "USER")
    void testDocumentAccessDenied() {
        // Document created for user1, accessed by user2
        assertThatThrownBy(() -> documentService.getDocument(1L))
            .isInstanceOf(AccessDeniedException.class);
    }
}

Production Considerations

Performance Tuning

@Configuration
public class ProductionAclConfig {
    
    @Bean
    public LookupStrategy optimizedLookupStrategy() {
        BasicLookupStrategy strategy = new BasicLookupStrategy(
            dataSource(),
            aclCache(),
            aclAuthorizationStrategy(),
            permissionGrantingStrategy()
        );
        
        // Optimize batch size for bulk operations
        strategy.setBatchSize(50);
        
        return strategy;
    }
    
    @Bean
    public CacheManager productionCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("aclCache");
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(100000)                    // Larger cache for production
            .expireAfterWrite(Duration.ofHours(1))   // Longer expiry
            .expireAfterAccess(Duration.ofMinutes(30))
            .recordStats()
        );
        return cacheManager;
    }
}

Monitoring and Metrics

@Configuration
public class AclMonitoringConfig {
    
    @Bean
    public CacheMetricsBinderConfiguration cacheMetrics() {
        return new CacheMetricsBinderConfiguration();
    }
    
    @EventListener
    public void handleAclCacheEvent(CacheEvictEvent event) {
        if ("aclCache".equals(event.getCacheName())) {
            // Log cache evictions for monitoring
            log.info("ACL cache evicted for key: {}", event.getKey());
        }
    }
}

@Component
public class AclPerformanceMonitor {
    
    private final MeterRegistry meterRegistry;
    
    public AclPerformanceMonitor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    @EventListener
    public void onAclLookup(AclLookupEvent event) {
        Timer.Sample sample = Timer.start(meterRegistry);
        // Record ACL lookup times
        sample.stop(Timer.builder("acl.lookup.duration")
            .tag("type", event.getType())
            .register(meterRegistry));
    }
}

Security Hardening

@Configuration
public class SecureAclConfig {
    
    @Bean
    public AclAuthorizationStrategy restrictiveAuthorizationStrategy() {
        // Only ADMIN can modify ACLs
        return new AclAuthorizationStrategyImpl(
            new SimpleGrantedAuthority("ROLE_ADMIN"),
            new SimpleGrantedAuthority("ROLE_ADMIN"),
            new SimpleGrantedAuthority("ROLE_ADMIN")
        );
    }
    
    @Bean
    public AuditLogger secureAuditLogger() {
        // Log to secure audit system
        return new Slf4jAuditLogger();
    }
    
    // Custom audit logger for compliance
    public static class Slf4jAuditLogger implements AuditLogger {
        private static final Logger auditLog = LoggerFactory.getLogger("ACL_AUDIT");
        
        @Override
        public void logIfNeeded(boolean granted, AccessControlEntry ace) {
            if (auditLog.isInfoEnabled()) {
                auditLog.info("ACL Decision: granted={}, sid={}, permission={}, object={}", 
                    granted, ace.getSid(), ace.getPermission(), ace.getAcl().getObjectIdentity());
            }
        }
    }
}

Troubleshooting

Common Issues

Database Connection Issues

// Problem: Connection pool exhaustion
// Solution: Tune connection pool settings
@Bean
public DataSource dataSource() {
    HikariConfig config = new HikariConfig();
    // ... other config
    
    // Prevent connection leaks
    config.setLeakDetectionThreshold(60000);
    config.setConnectionTestQuery("SELECT 1");
    
    return new HikariDataSource(config);
}

Performance Issues

// Problem: N+1 queries during permission checks
// Solution: Use batch loading and caching

@Service
public class OptimizedDocumentService {
    
    // Use @PostFilter with cache optimizer
    @PostFilter("hasPermission(filterObject, 'READ')")
    public List<Document> getDocuments() {
        return documentRepository.findAll(); // Single query + batch ACL load
    }
}

Permission Evaluation Errors

// Problem: Null pointer exceptions in permission expressions
// Solution: Add null checks

@PreAuthorize("@securityService.canAccess(#document)")
public void updateDocument(Document document) {
    // Custom security service handles null checks
}

@Service
public class SecurityService {
    public boolean canAccess(Object object) {
        if (object == null) return false;
        // Permission check logic
        return permissionEvaluator.hasPermission(authentication, object, "WRITE");
    }
}

Debug Configuration

# Enable ACL debug logging
logging.level.org.springframework.security.acls=DEBUG
logging.level.org.springframework.security.access=DEBUG

# Enable SQL logging to see ACL queries  
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

# Enable cache logging
logging.level.org.springframework.cache=DEBUG

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.

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-security--spring-security-acl

docs

acl-services.md

caching-performance.md

configuration.md

domain-model.md

index.md

permission-evaluation.md

strategy-interfaces.md

tile.json