SecurityDataInitializer.java

package com.taxonomy.security.config;

import com.taxonomy.security.model.AppRole;
import com.taxonomy.security.model.AppUser;
import com.taxonomy.security.repository.RoleRepository;
import com.taxonomy.security.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * Seeds the default roles and an initial admin user on first startup.
 * <p>
 * If the roles or admin user already exist (e.g. persistent database) this is a no-op.
 * The default admin password can be overridden via the {@code TAXONOMY_ADMIN_PASSWORD}
 * environment variable (or the equivalent Spring property {@code taxonomy.admin-password}).
 * <p>
 * Only active in form-login mode (without Keycloak). In the Keycloak profile,
 * users and roles are managed by the identity provider.
 */
@Component
@Profile("!keycloak")
public class SecurityDataInitializer implements ApplicationRunner {

    private static final Logger log = LoggerFactory.getLogger(SecurityDataInitializer.class);

    private final RoleRepository roleRepository;
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final String adminPassword;

    public SecurityDataInitializer(RoleRepository roleRepository,
                                   UserRepository userRepository,
                                   PasswordEncoder passwordEncoder,
                                   @Value("${taxonomy.admin-password:admin}") String adminPassword) {
        this.roleRepository = roleRepository;
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.adminPassword = adminPassword;
    }

    @Override
    public void run(ApplicationArguments args) {
        // Seed roles
        AppRole roleUser = findOrCreateRole("ROLE_USER");
        AppRole roleArchitect = findOrCreateRole("ROLE_ARCHITECT");
        AppRole roleAdmin = findOrCreateRole("ROLE_ADMIN");

        // Seed default admin user (if not present)
        if (userRepository.findByUsername("admin").isEmpty()) {
            AppUser admin = new AppUser();
            admin.setUsername("admin");
            admin.setPasswordHash(passwordEncoder.encode(adminPassword));
            admin.setEnabled(true);
            admin.setDisplayName("Administrator");
            admin.setRoles(Set.of(roleUser, roleArchitect, roleAdmin));
            userRepository.save(admin);
            log.info("Created default admin user (username=admin). Change the password immediately.");
        }

        // Warn if the default password is still in use
        if ("admin".equals(adminPassword)) {
            log.warn("SECURITY WARNING: Default admin password 'admin' is in use. "
                    + "Set TAXONOMY_ADMIN_PASSWORD environment variable to change it.");
        }
    }

    private AppRole findOrCreateRole(String name) {
        return roleRepository.findByName(name)
                .orElseGet(() -> roleRepository.save(new AppRole(name)));
    }
}