Spring Boot中的ACL安全性
我在Spring Boot应用程序中通过Java配置设置ACL时遇到问题。 我创建了一个小项目来重现问题。
我尝试过几种不同的方法。 我遇到的第一个问题是EhCache,在我修复之后(我假设我做了)我再也无法登录了,看起来所有的数据都消失了。
有4个类具有不同的配置:
ACLConfig1.class ACLConfig2.class ACLConfig3.class ACLConfig4.class
所有@PreAuthorize
和@PostAuthorize
注释都按预期工作,但hasPermission
除外。
控制器拥有4个端点:一个用于用户,一个用于管理员,一个用于公共,最后一个给我带来麻烦@PostAuthorize("hasPermission(returnObject,'administration')")
我很确定DB中的插入是正确的。 这个类是四个中的一个,也是我尝试过的最后一个类:
@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
之前,我首先要指出你没有按预期使用该文件。
初始化hibernate后, data.sql
执行data.sql
,通常只包含DML语句。 您的data.sql
包含DDL(模式)语句和DML(数据)语句。 这并不理想,因为你的一些DDL语句与hibernate的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
的执行是通过一个通过BeanPostProcessor
触发的ApplicationEvent
触发的。 这个BeanPostProcessor
( DataSourceInitializedPublisher
)是作为spring-boot的Hibernate / JPA自动配置的一部分创建的(参见org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
, org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher
and org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer
)。
通常, DataSourceInitializedPublisher
是在创建(嵌入) DataSource
之前创建的,并且一切都按预期工作,但通过定义自定义MethodSecurityExpressionHandler
,正常的bean创建顺序会改变。 在配置@EnableGlobalMethodSecurity
,您将自动导入GlobalMethodSecurityConfiguration
。
spring-security相关bean早期创建。 由于您的MethodSecurityExpressionHandler
需要ACL资源的DataSource
,并且与spring-security相关的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 (eg 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); } }
这应该可以解决您的“无数据”问题。 但为了让一切按预期工作,您需要再进行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中的getId()
方法设为public。
@Entity public class Car { //... public long getId() { return id; } //... }
在尝试在ACL权限评估期间确定对象的标识时,标准ObjectIdentityRetrievalStrategy
将查找公共方法’getId()’。
(请注意,我的答案基于ACLConfig4
。)