Spring 引导中的 ACL 安全性

ACL security in Spring Boot

我在 Spring 引导应用程序中通过 Java 配置设置 ACL 时遇到问题。我创建了一个小项目来重现这些问题。

我尝试了几种不同的方法。我遇到的第一个问题是 EhCache,在我修复它之后(我想我已经修复了)我无法再登录,看起来所有数据都消失了。

有 4 个 class 具有不同配置:

ACLConfig1.class
ACLConfig2.class
ACLConfig3.class
ACLConfig4.class

所有 @PreAuthorize@PostAuthorize 注释都按预期工作,hasPermission 除外。

控制器有 4 个端点:一个给用户,一个给管理员,一个 Public 最后一个让我头疼 @PostAuthorize("hasPermission(returnObject,'administration')")

我很确定数据库中的插入是正确的。这个 class 是四个之一,我试过的最后一个:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ACLConfig4 {

@Autowired
DataSource dataSource;


@Bean
public EhCacheBasedAclCache aclCache() {
    return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
}

@Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
    EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
    ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
    ehCacheFactoryBean.setCacheName("aclCache");
    return ehCacheFactoryBean;
}

@Bean
public EhCacheManagerFactoryBean aclCacheManager() {
    return new EhCacheManagerFactoryBean();
}

@Bean
public DefaultPermissionGrantingStrategy permissionGrantingStrategy() {
    ConsoleAuditLogger consoleAuditLogger = new ConsoleAuditLogger();
    return new DefaultPermissionGrantingStrategy(consoleAuditLogger);
}

@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
    return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMINISTRATOR"));
}

@Bean
public LookupStrategy lookupStrategy() {
    return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
}

@Bean
public JdbcMutableAclService aclService() {
    JdbcMutableAclService service = new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
    return service;
}

@Bean
public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
    return new DefaultMethodSecurityExpressionHandler();
}

@Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
    DefaultMethodSecurityExpressionHandler expressionHandler = defaultMethodSecurityExpressionHandler();
    expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
    expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
    return expressionHandler;
}


}

我在这里错过了什么?为什么我使用 ACLConfig3.class 或没有数据 ACLConfig4.class。有没有关于如何在 Spring Boot 中以编程方式配置它的示例?

您没有数据的原因有点难以找出。一旦您在配置中定义了一个 MethodSecurityExpressionHandler bean,数据库表中就没有数据了。这是因为您的 data.sql 文件没有执行。

在解释为什么 data.sql 没有被执行之前,我想首先指出您没有按预期使用该文件。

data.sql是hibernate初始化后由spring-boot执行的,一般只包含DML语句。您的 data.sql 包含 DDL(架构)语句和 DML(数据)语句。这并不理想,因为您的某些 DDL 语句与休眠的 hibernate.hbm2ddl.auto 行为冲突(请注意,当使用嵌入式 DataSource 时,spring-boot 使用 'create-drop')。您应该将 DDL 语句放在 schema.sql 中,将 DML 语句放在 data.sql 中。当您手动定义所有表时,您应该禁用 hibernate.hbm2ddl.auto(通过将 spring.jpa.hibernate.ddl-auto=none 添加到 applciation.properties)。

说了这么多,我们来看看为什么data.sql没有被执行

data.sql 的执行是通过 ApplicationEvent 触发的,而 ApplicationEvent 通过 BeanPostProcessor 触发。此 BeanPostProcessor (DataSourceInitializedPublisher) 是作为 spring-boot 的 Hibernate/JPA 自动配置的一部分创建的(参见 org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfigurationorg.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisherorg.springframework.boot.autoconfigure.jdbc.DataSourceInitializer).

通常 DataSourceInitializedPublisher 在(嵌入式)DataSource 创建之前创建,一切都按预期工作,但通过定义自定义 MethodSecurityExpressionHandler,正常的 bean 创建顺序会改变。 当您配置 @EnableGlobalMethodSecurity 时,您将自动导入 GlobalMethodSecurityConfiguration.

spring- 与安全相关的 bean 是在早期创建的。由于您的 MethodSecurityExpressionHandler 需要 DataSource 用于 ACL 内容,而 spring 安全相关的 bean 需要您的自定义 MethodSecurityExpressionHandler,因此 DataSource 比平时更早创建;事实上,它创建得太早了,以至于 spring-boot 的 DataSourceInitializedPublisher 还没有创建。 DataSourceInitializedPublisher 是稍后创建的,但由于它没有注意到 DataSource bean 的创建,它也不会触发 data.sql.

的执行

长话短说:安全配置改变了正常的 bean 创建顺序,导致 data.sql 未加载。

我想修复 bean 创建顺序可以解决问题,但由于我现在不知道如何(无需进一步实验)我提出以下解决方案:手动定义 DataSource 并处理数据初始化。

@Configuration
public class DataSourceConfig {
    @Bean
    public EmbeddedDatabase dataSource() {
        return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
                 //as your data.sql file contains both DDL & DML you might want to rename it (e.g. init.sql)
                .addScript("classpath:/data.sql")
                .build();
    }
}

由于您的 data.sql 文件包含您的应用程序所需的所有 DDL,您可以禁用 hibernate.hbm2ddl.auto。将 spring.jpa.hibernate.ddl-auto=none 添加到 applciation.properties

定义您自己的 DataSource spring-boot 的 DataSourceAutoConfiguration 通常会退出,但如果您想确定也可以排除它(可选)。

@SpringBootConfiguration
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
@ComponentScan
@EnableCaching
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

这应该可以解决您的 'no data' 问题。但是为了让一切都按预期工作,您需要再进行 2 次修改。

首先,你应该只定义一个MethodSecurityExpressionHandler bean。目前您正在定义 2 MethodSecurityExpressionHandler 个 bean。 Spring-security 不知道使用哪一个,而是(默默地)使用它自己的内部 MethodSecurityExpressionHandler。参见 org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration#setMethodSecurityExpressionHandler

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MyACLConfig {

    //...
    @Bean
    public MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler securityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
        securityExpressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
        securityExpressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
        return securityExpressionHandler;
    }

}

您需要做的最后一件事是在 Car public.

中创建 getId() 方法
@Entity
public class Car {
    //...    
    public long getId() {
        return id;
    }
    //...
}

在 ACL 权限评估期间尝试确定对象的身份时,标准 ObjectIdentityRetrievalStrategy 将寻找 public 方法 'getId()'。

(请注意,我的回答基于 ACLConfig4。)