Spring Boot中的ACL安全性

5er*_*5er 4 java acl spring-security spring-boot spring-cache

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

我尝试过几种不同的方法.我遇到的第一个问题是EhCache,在我修复之后(我假设我做了)我再也无法登录了,看起来所有的数据都消失了.

有4个类具有不同的配置:

ACLConfig1.class
ACLConfig2.class
ACLConfig3.class
ACLConfig4.class
Run Code Online (Sandbox Code Playgroud)

所有@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;
}


}
Run Code Online (Sandbox Code Playgroud)

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

Pie*_*ter 13

找不到你没有数据的原因有点棘手.只要MethodSecurityExpressionHandler在配置中定义bean,数据库表中就没有数据.这是因为您的data.sql文件未被执行.

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

data.sql在初始化hibernate之后由spring-boot执行,通常只包含DML语句.您data.sql包含DDL(模式)语句和DML(数据)语句.这并不理想,因为一些DDL语句与hibernate的hibernate.hbm2ddl.auto行为发生冲突(请注意,当使用嵌入式时,spring-boot使用'create-drop' DataSource).您应该将您的DDL语句放入schema.sqlDML语句中data.sql.正如你手动定义所有的表,你应该禁用hibernate.hbm2ddl.auto(加入spring.jpa.hibernate.ddl-auto=noneapplciation.properties).

话虽这么说,让我们来看看为什么data.sql不执行.

执行data.sqlApplicationEvent通过a触发的BeanPostProcessor.这个BeanPostProcessor(DataSourceInitializedPublisher)是作为spring-boot的Hibernate/JPA自动配置的一部分创建的(参见org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisherorg.springframework.boot.autoconfigure.jdbc.DataSourceInitializer).

通常DataSourceInitializedPublisher是在创建(嵌入)之前DataSource创建的,并且一切都按预期工作,但通过定义MethodSecurityExpressionHandler普通bean创建顺序的自定义.正如您配置的那样@EnableGlobalMethodSecurity,您将自动导入GlobalMethodSecurityConfiguration.

spring-security相关bean早期创建.因为你MethodSecurityExpressionHandler需要一个DataSourceACL的东西和spring-security相关的bean需要你的自定义MethodSecurityExpressionHandler,所以DataSource比平常更早创建; 实际上它是在春天创建的那么早就DataSourceInitializedPublisher创建了 - 尚未创建.它DataSourceInitializedPublisher是在稍后创建的,但由于它没有注意到DataSourcebean 的创建,因此它也不会触发执行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();
    }
}
Run Code Online (Sandbox Code Playgroud)

由于您的data.sql文件包含应用程序所需的所有DDL,因此您可以禁用它hibernate.hbm2ddl.auto.添加 spring.jpa.hibernate.ddl-auto=noneapplciation.properties.

定义自己的DataSource弹簧靴时DataSourceAutoConfiguration通常会退出,但如果你想确定你也可以将它排除(可选).

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

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

}
Run Code Online (Sandbox Code Playgroud)

这应该可以解决您的"无数据"问题.但为了让一切按预期工作,您需要再进行2次修改.

首先,您应该只定义一个MethodSecurityExpressionHandlerbean.目前你正在定义2个MethodSecurityExpressionHandlerbean.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;
    }

}
Run Code Online (Sandbox Code Playgroud)

您需要做的最后一件事是getId()在Car中公开方法.

@Entity
public class Car {
    //...    
    public long getId() {
        return id;
    }
    //...
}
Run Code Online (Sandbox Code Playgroud)

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

(请注意,我的答案基于ACLConfig4.)