Search code examples
javaspringspring-securityapplicationcontext

ApplicationListener<ContextRefreshedEvent> is not executed when the App is launched


I am new to programming, trying to make a simple application with the implementation of users, roles and privileges according to the manual. I use Sprint 3.3.3 and Java 17.

The manual suggested creating a Loader class that will populate the database when building the application. So it would be possible, without resorting to a pure SQL, to create the first admin, create roles and privileges for roles.

SetupDataLoader class implements ApplicationListener<ContextRefreshedEvent> interface. The class is marked with a @Component annotation. I also tried the onApplicationEvent method to mark with an annotation @EventListener.

But when the APP starts, the code from the class is not executed. Every time after launch, I check the contents of db and see that no new rows have been added to the User table. I also tried to output a message from that block of code to the console - it was not output.

MY APP

ENTITIES

@Entity
@Table(name = "users")
@Data
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @Column(name = "login")
    private String login;

    @Column(name = "password")
    private String password;

    @ManyToMany
    @JoinTable(
        name = "users_roles",
        joinColumns = @JoinColumn(
            name = "user_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(
            name = "role_id", referencedColumnName = "id"))
    private Collection<RoleEntity> roles;
    
    public UserEntity() {}
}

@Entity
@Table(name = "roles")
@Data
public class RoleEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

    @ManyToMany
    @JoinTable(
        name = "roles_privileges", 
        joinColumns = @JoinColumn(
          name = "role_id", referencedColumnName = "id"), 
        inverseJoinColumns = @JoinColumn(
          name = "privilege_id", referencedColumnName = "id"))
    private Collection<PrivilegeEntity> privileges;

    public RoleEntity() {}

    public RoleEntity(final String name) {
        this.name = name;
    }   
}

@Entity
@Table(name = "priveleges")
@Data
public class PrivilegeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "name")
    private String name;

    @ManyToMany(mappedBy = "privileges")
    private Collection<RoleEntity> roles;

    public PrivilegeEntity() {}

    public PrivilegeEntity(final String name) {
        this.name = name;
    }
}

REPOS

public interface UserRepo extends CrudRepository<UserEntity, Long> {

    Optional<UserEntity> findByLogin (String login);
    
}

public interface RoleRepo extends CrudRepository<RoleEntity, Long> {

    Optional<RoleEntity> findByName(String name);

}

public interface PrivilegeRepo extends CrudRepository<PrivilegeEntity, Long> {

    Optional<PrivilegeEntity> findByName(String name);

}

SEQURITY settings:

@EnableWebSecurity 
@Configuration
public class SequrityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .securityContext((securityContext) -> securityContext.requireExplicitSave(true))
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers(HttpMethod.GET, "/roleHierarchy")
                .hasRole("STAFF"));

        return http.build();
    }
    
    @Bean
    public SecurityExpressionHandler<FilterInvocation> customWebSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
        expressionHandler.setRoleHierarchy(roleHierarchy());
        return expressionHandler;
    }

    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        String hierarchy = "ROLE_ADMIN > ROLE_MODER \n ROLE_MODER > ROLE_STAFF";
        roleHierarchy.setHierarchy(hierarchy);
        return roleHierarchy;
    }
    
}

and finally

@Component
public class SetupDataLoader implements ApplicationListener<ContextRefreshedEvent> {

    private boolean alreadySetup = false;

    @Autowired
    UserRepo userRepo;

    @Autowired
    RoleRepo roleRepo;

    @Autowired
    PrivilegeRepo privilegeRepo;

    @Autowired
    PasswordEncoder passwordEncoder; 

    // API

    @Override
    @Transactional
    public void onApplicationEvent(final @NonNull ContextRefreshedEvent event) {
        if (alreadySetup) {
            return;
        }

        // == create init privileges
        final PrivilegeEntity readPrivilege = createPrivilege("READ_PRIVILEGE");
        final PrivilegeEntity userWritePrivilege = createPrivilege("USER_WRITE_PRIVILEGE");
        final PrivilegeEntity switchStatePrivilege = createPrivilege("CHANGE_STATE_PRIVILEGE");
        final PrivilegeEntity switchWritePrivilege = createPrivilege("SWITCH_WRITE_PRIVILEGE");
        final PrivilegeEntity commentWritePrivilege = createPrivilege("COMMENT_WRITE_PRIVILEGE");

        // == create init roles;
        final List<PrivilegeEntity> adminPrivileges = new ArrayList<>(Arrays
            .asList(readPrivilege, 
                    userWritePrivilege, 
                    switchStatePrivilege, 
                    switchWritePrivilege, 
                    commentWritePrivilege));

        final List<PrivilegeEntity> moderPrivileges = new ArrayList<>(Arrays
            .asList(readPrivilege, 
                    switchWritePrivilege, 
                    commentWritePrivilege));
                    
        final List<PrivilegeEntity> staffPrivileges = new ArrayList<>(Arrays.asList(readPrivilege));

        final RoleEntity adminRole = createRole("ROLE_ADMIN", adminPrivileges);
        createRole("ROLE_MODER", moderPrivileges);
        createRole("ROLE_STAFF", staffPrivileges);

        // == create init users FIRST ADMIN in db
        createUser("ADMIN", "ADMIN", new ArrayList<>(Arrays.asList(adminRole)));

        alreadySetup = true;
    }

    @Transactional
    public PrivilegeEntity createPrivilege(final String name) {
        Optional<PrivilegeEntity> optPrivilege = privilegeRepo.findByName(name);
        PrivilegeEntity privilege;

        if (optPrivilege.isPresent()) 
            privilege = optPrivilege.get();
        else {
            privilege = new PrivilegeEntity(name);
            privilege = privilegeRepo.save(privilege);
        }

        return privilege;
    }

    @Transactional
    public RoleEntity createRole(final String name, final Collection<PrivilegeEntity> privileges) {
        Optional<RoleEntity> optRole = roleRepo.findByName(name);
        RoleEntity role;

        if (optRole.isPresent()) 
            role = optRole.get();
        else {
            role = new RoleEntity(name);
        }
        role.setPrivileges(privileges);
        role = roleRepo.save(role);
        return role;
    }

    @Transactional
    public UserEntity createUser(final String login, final String password, final Collection<RoleEntity> roles) {
        Optional<UserEntity> optUser = userRepo.findByLogin(login);
        UserEntity user;

        if (optUser.isPresent()) 
            user = optUser.get();
        else {
            user = new UserEntity();
            user.setLogin(login);
            user.setPassword(passwordEncoder.encode(password));
        }
        user.setRoles(roles);
        user = userRepo.save(user);
        return user;
    }
}

I try various solutions from the Internet, but after each attempt I check my db and everything is the same:


prototype=# select * from users;
 id | login | password 
----+-------+----------
(0 rows)

My structure is like this:

🗁 src 
└─── 🗁 main
    ├─── 🗁 java/ru/dikun/prototype
        ├─── 🗁 config 
        │   ├───SequrityConfig.java
        │   └───SetupDataLoader.java
        ├─── 🗁 controllers
        ├─── 🗁 domain
        │   ├───PrivilegeEntity.java
        │   ├───RoleEntity.java
        │   └───UserEntity.java
        ├─── 🗁 repos
        │   ├───PrivilegeRepo.java
        │   ├───RoleRepo.java
        │   └───UserRepo.java
        ├─── 🗁 services
        │   └───UserService.java
        └───  PrototypeApplication.java
    └─── 🗁 resources
        └─── application.properties

Debug:

DEBUG 26790 --- [prototype] [  restartedMain] o.s.b.a.ApplicationAvailabilityBean      : Application availability state LivenessState changed to CORRECT
DEBUG 26790 --- [prototype] [  restartedMain] o.s.boot.devtools.restart.Restarter      : Creating new Restarter for thread Thread[main,5,main]
DEBUG 26790 --- [prototype] [  restartedMain] o.s.boot.devtools.restart.Restarter      : Immediately restarting application
DEBUG 26790 --- [prototype] [  restartedMain] o.s.boot.devtools.restart.Restarter      : Starting application ru.dikun.prototype.PrototypeApplication with URLs [file:/home/ogurchik/workspace/prototype/target/classes/]
DEBUG 26790 --- [prototype] [  restartedMain] o.s.b.a.ApplicationAvailabilityBean      : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
DEBUG 26790 --- [prototype] [ionShutdownHook] o.s.b.a.ApplicationAvailabilityBean      : Application availability state ReadinessState changed from ACCEPTING_TRAFFIC to REFUSING_TRAFFIC
DEBUG 26790 --- [prototype] [ionShutdownHook] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@1cbe84e7, started on ...

Please tell me why the code from the SetapDataLoader.java is not executed?


Solution

  • In fact, @SpringBootApplication(scanBasePackages={"ru.dikun.something", "ru.dikun.application"}) limited the scope of the scan when building the application. But without this annotation parameter, the application did not work in principle, which means it was necessary to solve the error. And the error was very common, which was answered on the Internet a long time ago, it was just necessary to solve it correctly, and not add parameters to the annotations.

    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    Field passwordEncoder in ru.dikun.prototype.config.SetupDataLoader required a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' that could not be found.
    
    The injection point has the following annotations:
            - @org.springframework.beans.factory.annotation.Autowired(required=true)
    
    
    Action:
    
    Consider defining a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' in your configuration.
    

    In order for the encoder to work, I had to actually declare a bin of the appropriate type, which was what the exception said. Added to my SequrityConfig:

        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    

    After that, everything worked. And The App started, and my SetupDataLoader class filled the database, as I wanted.