ACL security in Spring Boot

前端 未结 1 1640
余生分开走
余生分开走 2021-02-06 14:48

I am having issues setting up ACL through Java config in a Spring Boot application. I have created one small project to reproduce the issues.

I have tried a few differen

相关标签:
1条回答
  • 2021-02-06 15:12

    The reason why you have no data was a bit tricky to find out. As soon as you define a MethodSecurityExpressionHandler bean in your config, there is no data in the database tables. This is because your data.sql file isn't executed.

    Before explaining why data.sql isn't executed I'd first like to point out that you're not using the file as intended.

    data.sql is executed by spring-boot after hibernate has been initialized and normally only contains DML statements. Your data.sql contains both DDL (schema) statements and DML (data) statements. This isn't ideal as some of your DDL statements clash with hibernate's hibernate.hbm2ddl.auto behaviour (note that spring-boot uses 'create-drop' when an embedded DataSource is being used). You should put your DDL statements in schema.sql and your DML statements in data.sql. As you're manually defining all tables you should disable hibernate.hbm2ddl.auto (by adding spring.jpa.hibernate.ddl-auto=none to applciation.properties).

    That being said, let's take a look at why data.sql isn't executed.

    The execution of data.sql is triggered via an ApplicationEvent that's fired via a BeanPostProcessor. This BeanPostProcessor (DataSourceInitializedPublisher) is created as a part of spring-boot's Hibernate/JPA auto configuration (see org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher and org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer).

    Normally the DataSourceInitializedPublisher is created before the (embedded) DataSource is created and everything works as expected but by defining a custom MethodSecurityExpressionHandler the normal bean creation order alters. As you've configured @EnableGlobalMethodSecurity, your're automatically importing GlobalMethodSecurityConfiguration.

    spring-security related beans are created early on. As your MethodSecurityExpressionHandler requires a DataSource for the ACL stuff and the spring-security related beans require your custom MethodSecurityExpressionHandler, the DataSource is created earlier than usual; in fact it's created so early on that spring-boot's DataSourceInitializedPublisher isn't created yet. The DataSourceInitializedPublisher is created later on but as it didn't notice the creation of a DataSource bean, it also doesn't trigger the execution of data.sql.

    So long story short: the security configuration alters the normal bean creation order which results in data.sql not being loaded.

    I guess that fixing the bean creation order would do the trick, but as I don't now how (without further experimentation) I propose the following solution: manually define your DataSource and take care of data initialization.

    @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();
        }
    }
    

    As your data.sql file contains all DDL required by your application you can disable hibernate.hbm2ddl.auto. Add spring.jpa.hibernate.ddl-auto=none to applciation.properties.

    When defining your own DataSource spring-boot's DataSourceAutoConfiguration normally back's out but if you want to be sure you can also exclude it (optional).

    @SpringBootConfiguration
    @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
    @ComponentScan
    @EnableCaching
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    
    }
    

    This should fix your 'no data' problem. But in order to get everything working as expected you need to make 2 more modifications.

    First of all, you should only define one MethodSecurityExpressionHandler bean. Currently you're defining 2 MethodSecurityExpressionHandler beans. Spring-security won't know which one to use and will (silently) use it's own internal MethodSecurityExpressionHandler instead. See 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;
        }
    
    }
    

    The last thing you need to do is make the getId() method in Car public.

    @Entity
    public class Car {
        //...    
        public long getId() {
            return id;
        }
        //...
    }
    

    The standard ObjectIdentityRetrievalStrategy will look for a public method 'getId()' when trying to determine an object's identity during ACL permission evaluation.

    (Note that I've based my answer upon ACLConfig4.)

    0 讨论(0)
提交回复
热议问题