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.HibernateJpaAutoConfiguration
、org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher
和 org.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
。)
我在 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.HibernateJpaAutoConfiguration
、org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher
和 org.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
。)