Spring Boot + MySQL 创建超级管理员

Spring Boot系统创建超管

  • 实现方式
    • [1. 数据库脚本初始化(最直接的方式)](#1. 数据库脚本初始化(最直接的方式))
    • [2. Spring Boot 启动时自动创建(代码级初始化)](#2. Spring Boot 启动时自动创建(代码级初始化))
      • [2.1 实体类定义](#2.1 实体类定义)
      • [2.2 Repository 接口](#2.2 Repository 接口)
      • [2.3 初始化组件](#2.3 初始化组件)
      • [2.4 配置密码加密器](#2.4 配置密码加密器)
    • [3. 通过接口手动创建(最安全的方式)](#3. 通过接口手动创建(最安全的方式))
      • [3.1 初始化接口](#3.1 初始化接口)
      • [3.2 服务层实现](#3.2 服务层实现)
      • [3.3 数据传输对象(DTO)](#3.3 数据传输对象(DTO))
    • [4. 使用 Flyway/Liquibase 数据库版本控制](#4. 使用 Flyway/Liquibase 数据库版本控制)
      • [4.1 添加依赖(Maven)](#4.1 添加依赖(Maven))
      • [4.2 创建脚本文件](#4.2 创建脚本文件)
      • [4.3 配置 Flyway(application.properties)](#4.3 配置 Flyway(application.properties))
    • 各方法对比与选择建议
  • 根据项目规模及复杂度选择方式

实现方式

1. 数据库脚本初始化(最直接的方式)

通过预执行 SQL 脚本创建管理员用户,适合数据库初始化阶段使用。

实现步骤:

  1. 创建用户表和角色表
  2. 插入预设角色
  3. 插入加密后的管理员用户
sql 复制代码
-- 1. 创建用户表
CREATE TABLE `sys_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '加密密码',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:正常)',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户';

-- 2. 创建角色表
CREATE TABLE `sys_role` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(50) NOT NULL COMMENT '角色名称',
  `role_key` varchar(50) NOT NULL COMMENT '角色标识',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_role_key` (`role_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

-- 3. 创建用户角色关联表
CREATE TABLE `sys_user_role` (
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`user_id`,`role_id`),
  KEY `fk_role_id` (`role_id`),
  CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`),
  CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';

-- 4. 插入预设角色(超级管理员角色)
INSERT INTO `sys_role` (`role_name`, `role_key`) VALUES ('超级管理员', 'ROLE_SUPER_ADMIN');

-- 5. 插入管理员用户(密码使用BCrypt加密,原始密码:Admin@123456)
INSERT INTO `sys_user` (`username`, `password`, `email`) 
VALUES (
  'admin', 
  '$2a$10$G9h.C4O4X2r0U8F5L2j/9O3lQ5VQZJZJZJZJZJZJZJZJZJZJZJZ',  -- BCrypt加密后的密码
  'admin@example.com'
);

-- 6. 关联用户与角色
INSERT INTO `sys_user_role` (`user_id`, `role_id`) 
VALUES (
  (SELECT id FROM sys_user WHERE username = 'admin'),
  (SELECT id FROM sys_role WHERE role_key = 'ROLE_SUPER_ADMIN')
);

注意事项:

  • 密码必须使用加密算法(如 BCrypt),不可明文存储
  • 可通过 在线 BCrypt 加密工具 生成加密后的密码
  • 脚本执行时机:数据库初始化时(如项目首次部署)

2. Spring Boot 启动时自动创建(代码级初始化)

利用 Spring 的 CommandLineRunner 接口,在应用启动时自动检查并创建管理员用户。

CommandLineRunner接口用于定义应用程序启动后需要立即执行的逻辑 。(编写一个类实现该接口,此时需要重写该接口中的run()方法,将需要实现的代码编写至该run()方法中,该方法会在 Spring Boot 应用完全启动后执行)

实现步骤:

  1. 定义用户和角色实体类
  2. 创建数据访问层(Repository)
  3. 实现初始化逻辑

2.1 实体类定义

java 复制代码
// User.java
@Entity
@Table(name = "sys_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(unique = true, nullable = false)
    private String username;
    
    @Column(nullable = false)
    private String password;
    
    private String email;
    
    private Integer status = 1; // 1-正常,0-禁用
    
    @Column(name = "create_time")
    private LocalDateTime createTime = LocalDateTime.now();
    
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "sys_user_role",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles = new HashSet<>();
    
    // 省略 getter/setter
}

// Role.java
@Entity
@Table(name = "sys_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "role_name")
    private String roleName;
    
    @Column(name = "role_key", unique = true)
    private String roleKey;
    
    // 省略 getter/setter
}

2.2 Repository 接口

java 复制代码
// UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

// RoleRepository.java
public interface RoleRepository extends JpaRepository<Role, Long> {
    Optional<Role> findByRoleKey(String roleKey);
}

2.3 初始化组件

java 复制代码
@Component
public class AdminUserInitializer implements CommandLineRunner {

    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private RoleRepository roleRepository;
    
    @Autowired
    private PasswordEncoder passwordEncoder; 		// Spring Security 提供的密码加密器
    
    // CommandLineRunner 的 run() 方法会在 Spring Boot 应用完全启动后执行,具体时机:① 所有 Spring 容器中的 Bean 都已初始化完成(依赖注入完成),② 嵌入式服务器(如 Tomcat)已启动但还未开始接收请求,③ 执行顺序在 @PostConstruct 注解之后(@PostConstruct 是 Bean 初始化时执行,早于 CommandLineRunner)。简单说:应用启动成功后,第一个用户请求到达前,run() 方法会被自动调用。
    // 确保应用启动时,数据库中存在一个可用的超级管理员账户(如果不存在则自动创建)。
    @Override
    public void run(String... args) throws Exception {
        createAdminRoleIfNotExists();
        createAdminUserIfNotExists();
    }

    // 创建超级管理员角色(如果不存在)
    private void createAdminRoleIfNotExists() {
        if (roleRepository.findByRoleKey("ROLE_SUPER_ADMIN").isEmpty()) {
            Role adminRole = new Role();
            adminRole.setRoleName("超级管理员");
            adminRole.setRoleKey("ROLE_SUPER_ADMIN");
            roleRepository.save(adminRole);
        }
    }

    // 创建管理员用户(如果不存在)
    private void createAdminUserIfNotExists() {
        if (userRepository.findByUsername("admin").isEmpty()) {
            // 获取管理员角色
            Role adminRole = roleRepository.findByRoleKey("ROLE_SUPER_ADMIN")
                .orElseThrow(() -> new RuntimeException("超级管理员角色不存在"));
            
            // 创建用户
            User adminUser = new User();
            adminUser.setUsername("admin");
            adminUser.setPassword(passwordEncoder.encode("Admin@123456")); // 加密密码
            adminUser.setEmail("admin@example.com");
            adminUser.getRoles().add(adminRole);
            
            userRepository.save(adminUser);
            System.out.println("超级管理员用户创建成功:admin/Admin@123456");
        }
    }
}

2.4 配置密码加密器

java 复制代码
@Configuration
public class SecurityConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(); // 使用BCrypt加密算法
    }
}

优点:

  • 自动化程度高,无需手动执行脚本
  • 可通过代码逻辑灵活控制创建条件

3. 通过接口手动创建(最安全的方式)

系统部署后,通过一个临时的初始化接口创建管理员用户,适合生产环境。

实现步骤:

  1. 创建一个初始化接口
  2. 添加安全限制(如仅允许本地访问)
  3. 通过接口调用创建管理员

3.1 初始化接口

java 复制代码
@RestController
@RequestMapping("/init")
public class InitController {

    @Autowired
    private UserService userService;
    
    // 仅允许本地访问(生产环境安全限制)
    @PostMapping("/admin")
    public Result createAdmin(@RequestBody AdminInitDTO dto) {
        // 检查请求来源是否为本地
        if (!isLocalRequest()) {
            return Result.fail("仅允许本地访问");
        }
        
        // 检查管理员是否已存在
        if (userService.existsByUsername("admin")) {
            return Result.fail("管理员已存在");
        }
        
        // 创建管理员
        userService.createAdmin(dto);
        return Result.success("管理员创建成功");
    }
    
    // 判断请求是否来自本地
    private boolean isLocalRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String ip = request.getRemoteAddr();
        return "127.0.0.1".equals(ip) || "localhost".equals(ip);
    }
}

3.2 服务层实现

java 复制代码
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private RoleRepository roleRepository;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Transactional
    public void createAdmin(AdminInitDTO dto) {
        // 创建角色
        Role adminRole = new Role();
        adminRole.setRoleName("超级管理员");
        adminRole.setRoleKey("ROLE_SUPER_ADMIN");
        roleRepository.save(adminRole);
        
        // 创建用户
        User adminUser = new User();
        adminUser.setUsername("admin");
        adminUser.setPassword(passwordEncoder.encode(dto.getPassword()));
        adminUser.setEmail(dto.getEmail());
        adminUser.getRoles().add(adminRole);
        
        userRepository.save(adminUser);
    }
    
    public boolean existsByUsername(String username) {
        return userRepository.findByUsername(username).isPresent();
    }
}

3.3 数据传输对象(DTO)

java 复制代码
public class AdminInitDTO {
    private String password; // 管理员密码(前端传入明文,后端加密)
    private String email;    // 管理员邮箱
    
    // 省略 getter/setter
}

优点:

  • 安全性高,避免硬编码密码
  • 可在系统部署后手动触发,灵活控制时机
  • 适合生产环境使用

4. 使用 Flyway/Liquibase 数据库版本控制

通过数据库版本控制工具管理初始化脚本,适合团队协作和版本化部署。

实现步骤:

  1. 添加 Flyway 依赖
  2. 创建初始化脚本
  3. 配置 Flyway

4.1 添加依赖(Maven)

xml 复制代码
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

4.2 创建脚本文件

src/main/resources/db/migration 目录下创建 V1__init_admin_user.sql

sql 复制代码
-- 内容同方法一中的SQL脚本
CREATE TABLE `sys_user` (...);
CREATE TABLE `sys_role` (...);
CREATE TABLE `sys_user_role` (...);
INSERT INTO `sys_role` (...) VALUES (...);
INSERT INTO `sys_user` (...) VALUES (...);
INSERT INTO `sys_user_role` (...) VALUES (...);

4.3 配置 Flyway(application.properties)

properties 复制代码
# Flyway配置
spring.flyway.enabled=true
spring.flyway.baseline-on-migrate=true
spring.flyway.clean-disabled=true # 禁用清理功能(生产环境安全)

优点:

  • 脚本版本化管理,适合团队协作
  • 自动执行,无需手动干预
  • 支持数据库变更的追溯和回滚

各方法对比与选择建议

方法 适用场景 安全性 自动化程度
数据库脚本初始化 简单项目、快速部署
启动时自动创建 开发/测试环境、小型项目
接口手动创建 生产环境、对安全性要求高
Flyway/Liquibase 团队协作、大型项目、版本化部署

推荐方案

  • 开发环境:使用 启动时自动创建Flyway
  • 生产环境:优先使用 接口手动创建Flyway(配合加密配置)

无论选择哪种方法,都需遵循以下安全原则:

  1. 密码必须加密存储(推荐 BCrypt 或 Argon2)
  2. 首次登录后强制修改默认密码
  3. 最小权限原则(超级管理员仅用于系统初始化)
  4. 敏感配置(如初始密码)避免硬编码,通过环境变量或配置中心注入

根据项目规模及复杂度选择方式

在 Spring Boot + MySQL 系统中创建第一个超级管理员用户的方案选择,应根据项目规模和复杂度进行调整。以下是针对大、中、小型项目在生产环境中的最佳实践:

1. 小型项目(团队≤5人,功能简单)

推荐方案:应用启动时自动创建(带安全开关)

适合场景:快速部署、维护成本低的小型应用,如内部管理工具、简单的业务系统。

实现步骤:

  1. 数据库表结构(同上一回答,包含用户、角色和关联表)

  2. 安全配置类

    java 复制代码
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder(12); // 工作因子12,安全性较高
        }
    }
  3. 带开关的初始化器

    java 复制代码
    @Component
    public class AdminUserInitializer implements CommandLineRunner {
    
        @Value("${app.initialize-admin:false}") // 默认关闭,生产环境需显式开启
        private boolean initializeAdmin;
        
        @Value("${app.admin.username:admin}")
        private String adminUsername;
        
        @Value("${app.admin.email:admin@example.com}")
        private String adminEmail;
        
        @Value("${app.admin.initial-password}") // 必须在环境变量中设置
        private String initialPassword;
    
        private final UserRepository userRepository;
        private final RoleRepository roleRepository;
        private final PasswordEncoder passwordEncoder;
    
        @Autowired
        public AdminUserInitializer(UserRepository userRepository, 
                                   RoleRepository roleRepository,
                                   PasswordEncoder passwordEncoder) {
            this.userRepository = userRepository;
            this.roleRepository = roleRepository;
            this.passwordEncoder = passwordEncoder;
        }
    
        @Override
        public void run(String... args) {
            if (initializeAdmin) {
                createAdminUserIfNotExists();
            }
        }
    
        private void createAdminUserIfNotExists() {
            if (userRepository.findByUsername(adminUsername).isEmpty()) {
                // 验证初始密码强度
                if (!isPasswordStrong(initialPassword)) {
                    throw new IllegalArgumentException("Initial password does not meet security requirements");
                }
                
                Role adminRole = roleRepository.findByName("ROLE_ADMIN")
                    .orElseGet(() -> {
                        Role role = new Role();
                        role.setName("ROLE_ADMIN");
                        return roleRepository.save(role);
                    });
    
                User adminUser = new User();
                adminUser.setUsername(adminUsername);
                adminUser.setPassword(passwordEncoder.encode(initialPassword));
                adminUser.setEmail(adminEmail);
                adminUser.setEnabled(true);
                adminUser.setForcePasswordChange(true); // 强制首次登录修改密码
                adminUser.getRoles().add(adminRole);
    
                userRepository.save(adminUser);
                log.info("Admin user created successfully. Username: {}", adminUsername);
            }
        }
        
        // 密码强度检查
        private boolean isPasswordStrong(String password) {
            return password.length() >= 12 
                && password.matches(".*[A-Z].*")
                && password.matches(".*[a-z].*")
                && password.matches(".*\\d.*")
                && password.matches(".*[!@#$%^&*()].*");
        }
    }
  4. 配置文件

    properties 复制代码
    # application-prod.properties
    app.initialize-admin=false  # 默认关闭
    app.admin.username=admin
    app.admin.email=admin@example.com
    # 生产环境通过环境变量设置: export APP_ADMIN_INITIAL_PASSWORD=强密码
  5. 首次启动命令

    bash 复制代码
    # 仅首次启动时使用,创建完成后禁用
    APP_ADMIN_INITIAL_PASSWORD='StrongP@ssw0rd2024' java -jar app.jar --spring.profiles.active=prod --app.initialize-admin=true

优点:实现简单,部署便捷,适合资源有限的小团队

缺点:安全性中等,需要手动确保初始化后关闭开关

2. 中型项目(团队5-20人,业务中等复杂度)

推荐方案:命令行工具初始化 + 密码重置机制

适合场景:有专职运维人员,需要更规范的部署流程的应用,如企业级SaaS、业务管理系统。

实现步骤:

  1. 创建专用的命令行工具

    java 复制代码
    @ShellComponent
    public class AdminUserCommand {
    
        private final AdminUserService adminUserService;
        private final Logger log = LoggerFactory.getLogger(AdminUserCommand.class);
    
        @Autowired
        public AdminUserCommand(AdminUserService adminUserService) {
            this.adminUserService = adminUserService;
        }
    
        @ShellMethod(key = "create-admin", value = "Creates the initial admin user")
        public String createAdmin(
                @ShellOption(help = "Admin username") String username,
                @ShellOption(help = "Admin email") String email,
                @ShellOption(help = "Temporary password") String tempPassword) {
            
            try {
                // 检查用户是否已存在
                if (adminUserService.existsByUsername(username)) {
                    return "Error: Admin user with username '" + username + "' already exists";
                }
                
                // 创建管理员用户
                String result = adminUserService.createInitialAdmin(username, email, tempPassword);
                log.info("Admin user '{}' created successfully", username);
                return result + "\nPlease ensure to change this password immediately after first login";
            } catch (Exception e) {
                log.error("Failed to create admin user", e);
                return "Failed to create admin user: " + e.getMessage();
            }
        }
        
        @ShellMethod(key = "generate-admin-token", value = "Generates a one-time token for admin password reset")
        public String generateAdminToken(
                @ShellOption(help = "Admin username") String username) {
            
            try {
                String token = adminUserService.generatePasswordResetToken(username);
                return "One-time reset token (valid for 10 minutes): " + token;
            } catch (Exception e) {
                return "Error generating token: " + e.getMessage();
            }
        }
    }
  2. 服务层实现

    java 复制代码
    @Service
    @Transactional
    public class AdminUserService {
    
        private final UserRepository userRepository;
        private final RoleRepository roleRepository;
        private final PasswordEncoder passwordEncoder;
        private final PasswordResetTokenRepository tokenRepository;
    
        // 构造函数注入依赖...
    
        public String createInitialAdmin(String username, String email, String tempPassword) {
            // 密码强度验证...
            
            Role adminRole = getOrCreateAdminRole();
            
            User admin = new User();
            admin.setUsername(username);
            admin.setEmail(email);
            admin.setPassword(passwordEncoder.encode(tempPassword));
            admin.setEnabled(true);
            admin.setForcePasswordChange(true);
            admin.setLastPasswordChangeDate(LocalDateTime.now());
            admin.getRoles().add(adminRole);
            
            userRepository.save(admin);
            return "Admin user created with username: " + username;
        }
        
        public String generatePasswordResetToken(String username) {
            User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));
                
            // 生成唯一令牌,有效期10分钟
            String token = UUID.randomUUID().toString();
            PasswordResetToken resetToken = new PasswordResetToken();
            resetToken.setToken(token);
            resetToken.setUser(user);
            resetToken.setExpiryDate(LocalDateTime.now().plusMinutes(10));
            
            tokenRepository.save(resetToken);
            return token;
        }
        
        // 其他方法...
    }
  3. 使用流程

    bash 复制代码
    # 1. 启动应用的命令行模式
    java -jar app.jar --spring.shell.interactive.enabled=true
    
    # 2. 在shell中执行创建命令
    shell:> create-admin --username superadmin --email admin@company.com --tempPassword 'TempP@ss123!'
    
    # 3. 生成密码重置令牌
    shell:> generate-admin-token --username superadmin
    
    # 4. 通过前端页面或API使用令牌重置密码

优点:安全性高,操作可审计,符合规范流程

缺点:需要额外开发命令行工具,增加少量开发成本

3.大型项目(团队>20人,复杂业务系统)

推荐方案:初始化脚本 + 密钥管理 + 多因素认证

适合场景:企业核心系统、金融科技、高安全性要求的应用,有完善的DevOps流程。

实现步骤:

  1. 数据库设计与迁移策略 :使用FlywayLiquibase进行版本化数据库迁移,确保表结构创建和初始数据插入可追溯。

    sql 复制代码
    # 数据表创建`V1_schema_init.sql`
    -- 用户表
    CREATE TABLE users (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        username VARCHAR(50) NOT NULL UNIQUE,
        password_hash VARCHAR(255) NOT NULL,
        email VARCHAR(100) NOT NULL UNIQUE,
        enabled BOOLEAN NOT NULL DEFAULT FALSE,
        force_password_change BOOLEAN NOT NULL DEFAULT TRUE,
        last_password_change TIMESTAMP,
        mfa_enabled BOOLEAN NOT NULL DEFAULT FALSE,
        created_by VARCHAR(50) NOT NULL,
        created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updated_by VARCHAR(50),
        updated_at TIMESTAMP,
        CONSTRAINT valid_email CHECK (email REGEXP '^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$')
    );
    
    -- 角色表
    CREATE TABLE roles (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(50) NOT NULL UNIQUE,
        description VARCHAR(255),
        created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
    
    -- 权限表
    CREATE TABLE permissions (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(100) NOT NULL UNIQUE,
        description VARCHAR(255),
        created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
    
    -- 用户角色关联表
    CREATE TABLE user_roles (
        user_id BIGINT NOT NULL,
        role_id BIGINT NOT NULL,
        assigned_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
        assigned_by VARCHAR(50) NOT NULL,
        PRIMARY KEY (user_id, role_id),
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
        FOREIGN KEY (role_id) REFERENCES roles(id)
    );
    
    -- 角色权限关联表
    CREATE TABLE role_permissions (
        role_id BIGINT NOT NULL,
        permission_id BIGINT NOT NULL,
        PRIMARY KEY (role_id, permission_id),
        FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
        FOREIGN KEY (permission_id) REFERENCES permissions(id)
    );
    
    -- 操作审计日志表
    CREATE TABLE audit_logs (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT,
        operation VARCHAR(100) NOT NULL,
        entity_type VARCHAR(50) NOT NULL,
        entity_id VARCHAR(50),
        details JSON,
        ip_address VARCHAR(45),
        user_agent TEXT,
        created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (user_id) REFERENCES users(id)
    );
    sql 复制代码
    # 数据库初始化脚本`V2_initial_roles.sql`
    -- 创建基础角色
    INSERT INTO roles (name, description) VALUES 
    	('ROLE_SUPER_ADMIN', '系统最高权限管理员'),
    	('ROLE_ADMIN', '普通管理员'),
    	('ROLE_SECURITY_AUDITOR', '安全审计员');
    
    -- 插入核心权限
    INSERT INTO permissions (name, description) VALUES
    	('USER_CREATE', '创建用户'),
    	('USER_DELETE', '删除用户'),
    	('USER_UPDATE', '更新用户'),
    	('USER_VIEW', '查看用户'),
    	('ROLE_MANAGE', '管理角色'),
    	('PERMISSION_MANAGE', '管理权限'),
    	('SYSTEM_CONFIG', '系统配置'),
    	('AUDIT_VIEW', '查看审计日志');	
    
    -- 为超级管理员角色分配所有权限
    INSERT INTO role_permissions (role_id, permission_id)
    SELECT 
        (SELECT id FROM roles WHERE name = 'ROLE_SUPER_ADMIN'),
        id 
    FROM permissions;
  2. 密码生成与管理工具:开发专用工具生成符合 NIST 标准的强密码,并使用 BCrypt 算法 (工作因子 12+) 进行哈希处理。

    java 复制代码
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import java.security.SecureRandom;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class AdminPasswordUtil {
        private static final int PASSWORD_LENGTH = 20;
        private static final BCryptPasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder(14);
        private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        private static final String LOWER = "abcdefghijklmnopqrstuvwxyz";
        private static final String DIGITS = "0123456789";
        private static final String SYMBOLS = "!@#$%^&*()_-+=[{]};:<>|./?";
        private static final SecureRandom RANDOM = new SecureRandom();
    
        public static void main(String[] args) {
            // 生成符合NIST标准的强密码
            String rawPassword = generateSecurePassword();
            String encodedPassword = PASSWORD_ENCODER.encode(rawPassword);
            
            System.out.println("=== 生成的超级管理员初始密码 ===");
            System.out.println("明文密码: " + rawPassword);
            System.out.println("BCrypt哈希: " + encodedPassword);
            System.out.println("==============================");
            System.out.println("警告: 此密码仅显示一次,使用后立即销毁记录");
        }
    
        private static String generateSecurePassword() {
            // 确保密码包含所有类型的字符
            List<Character> passwordChars = new ArrayList<>(PASSWORD_LENGTH);
            
            // 至少包含每种类型的字符
            passwordChars.add(UPPER.charAt(RANDOM.nextInt(UPPER.length())));
            passwordChars.add(LOWER.charAt(RANDOM.nextInt(LOWER.length())));
            passwordChars.add(DIGITS.charAt(RANDOM.nextInt(DIGITS.length())));
            passwordChars.add(SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())));
            
            // 填充剩余字符
            String allChars = UPPER + LOWER + DIGITS + SYMBOLS;
            for (int i = 4; i < PASSWORD_LENGTH; i++) {
                passwordChars.add(allChars.charAt(RANDOM.nextInt(allChars.length())));
            }
            
            // 随机打乱顺序
            Collections.shuffle(passwordChars, RANDOM);
            
            // 转换为字符串
            StringBuilder password = new StringBuilder(PASSWORD_LENGTH);
            for (Character c : passwordChars) {
                password.append(c);
            }
            
            return password.toString();
        }
    }
  3. 创建超级管理员脚本:使用单独的数据库迁移脚本创建超级管理员,密码哈希通过前面的工具生成。

    sql 复制代码
    # 创建超管的数据库脚本`V3_create_super_admin.sql`
    -- 注意: 此脚本中的密码哈希必须通过AdminPasswordUtil生成
    -- 生产环境部署前必须替换为新生成的哈希值
    INSERT INTO users (
        username, 
        password_hash, 
        email, 
        enabled, 
        force_password_change,
        created_by
    ) VALUES (
        'superadmin',
        -- 这里替换为通过AdminPasswordUtil生成的BCrypt哈希
        '$2a$14$示例哈希值需替换',
        'security-admin@yourcompany.com',
        TRUE,
        TRUE,
        'SYSTEM_INIT'
    );
    
    -- 为超级管理员分配角色
    INSERT INTO user_roles (user_id, role_id, assigned_by)
    VALUES (
        (SELECT id FROM users WHERE username = 'superadmin'),
        (SELECT id FROM roles WHERE name = 'ROLE_SUPER_ADMIN'),
        'SYSTEM_INIT'
    );
    
    -- 记录审计日志
    INSERT INTO audit_logs (
        operation, 
        entity_type, 
        entity_id, 
        details, 
        created_at
    ) VALUES (
        'INITIAL_ADMIN_CREATED',
        'USER',
        (SELECT id FROM users WHERE username = 'superadmin'),
        JSON_OBJECT(
            'action', 'Initial super admin creation',
            'username', 'superadmin',
            'creation_method', 'database_migration',
            'security_note', 'Force password change required'
        ),
        CURRENT_TIMESTAMP
    );
  4. 部署流程与安全控制:整合 CI/CD 流程,确保所有操作可审计并符合安全规范。

    yaml 复制代码
    # deploy-init.yaml
    name: Deploy Initial Setup
    
    on:
      workflow_dispatch:
        inputs:
          environment:
            description: 'Deployment environment'
            required: true
            default: 'production'
            type: choice
            options:
              - production
              - staging
          approver:
            description: 'Approver username'
            required: true
          ticketNumber:
            description: 'Change management ticket number'
            required: true
    
    jobs:
      security-scan:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Run SQL linting
            run: |
              # 检查SQL脚本安全性
              sqlfluff lint src/main/resources/db/migration/
              
      generate-credentials:
        needs: security-scan
        runs-on: ubuntu-latest
        outputs:
          password_hash: ${{ steps.generate.outputs.hash }}
        steps:
          - uses: actions/checkout@v4
          - name: Generate admin credentials
            id: generate
            run: |
              # 编译密码生成工具
              javac -d target src/main/java/com/company/util/AdminPasswordUtil.java
              # 生成密码并捕获输出
              output=$(java -cp target com.company.util.AdminPasswordUtil)
              raw_password=$(echo "$output" | grep "明文密码" | cut -d: -f2 | xargs)
              password_hash=$(echo "$output" | grep "BCrypt哈希" | cut -d: -f2 | xargs)
              
              # 输出哈希供后续步骤使用
              echo "hash=$password_hash" >> $GITHUB_OUTPUT
              
              # 将明文密码存储到安全的密钥管理系统
              aws secretsmanager create-secret \
                --name "${{ github.ref_name }}/admin/initial-password" \
                --secret-string "$raw_password" \
                --description "Initial superadmin password created by ${{ github.actor }} for ticket ${{ github.event.inputs.ticketNumber }}"
              
              # 记录审计信息
              echo "Initial password generated for ${{ github.event.inputs.environment }} by ${{ github.actor }}" >> audit.log
              
      update-migration-script:
        needs: generate-credentials
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Replace password hash in migration script
            run: |
              # 使用生成的哈希值替换脚本中的占位符
              sed -i "s|'\\$2a\\$14\\$示例哈希值需替换'|'${{ needs.generate-credentials.outputs.password_hash }}'|g" \
                src/main/resources/db/migration/V3__create_super_admin.sql
          
          - name: Commit updated script
            run: |
              git config --global user.name "CI Bot"
              git config --global user.email "ci@company.com"
              git add src/main/resources/db/migration/V3__create_super_admin.sql
              git commit -m "Update admin password hash for ${{ github.event.inputs.environment }} [ticket: ${{ github.event.inputs.ticketNumber }}]"
              git push
              
      run-migrations:
        needs: update-migration-script
        runs-on: ubuntu-latest
        environment: ${{ github.event.inputs.environment }}
        steps:
          - uses: actions/checkout@v4
          - name: Run database migrations
            run: |
              # 执行Flyway迁移
              java -jar flyway/flyway-commandline-10.10.0.jar migrate \
                -url=jdbc:mysql://${{ secrets.DB_HOST }}:${{ secrets.DB_PORT }}/${{ secrets.DB_NAME }} \
                -user=${{ secrets.DB_USER }} \
                -password=${{ secrets.DB_PASSWORD }} \
                -locations=filesystem:src/main/resources/db/migration
          
          - name: Log deployment
            run: |
              # 记录部署信息到审计系统
              curl -X POST ${{ secrets.AUDIT_SERVICE_URL }} \
                -H "Authorization: Bearer ${{ secrets.AUDIT_SERVICE_TOKEN }}" \
                -H "Content-Type: application/json" \
                -d '{
                  "event": "SUPER_ADMIN_CREATED",
                  "environment": "${{ github.event.inputs.environment }}",
                  "user": "${{ github.actor }}",
                  "approver": "${{ github.event.inputs.approver }}",
                  "ticket": "${{ github.event.inputs.ticketNumber }}",
                  "timestamp": "'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"
                }'
  5. 首次登录强制安全措施:实现首次登录必须修改密码并配置多因素认证 (MFA)。

    java 复制代码
    import jakarta.servlet.FilterChain;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.web.filter.OncePerRequestFilter;
    import java.io.IOException;
    
    public class ForceChangePasswordFilter extends OncePerRequestFilter {
    
        private final UserService userService;
    
        // 构造函数注入UserService
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, 
                                       HttpServletResponse response, 
                                       FilterChain filterChain) throws ServletException, IOException {
            
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            
            if (auth != null && auth.isAuthenticated() && !request.getRequestURI().contains("/api/auth/change-password")) {
                String username = auth.getName();
                boolean forceChange = userService.needsPasswordChange(username);
                
                if (forceChange) {
                    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    response.setContentType("application/json");
                    response.getWriter().write("{\"error\": \"FORCE_PASSWORD_CHANGE_REQUIRED\", \"message\": \"You must change your password before proceeding\"}");
                    return;
                }
            }
            
            filterChain.doFilter(request, response);
        }
    }
    java 复制代码
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.web.SecurityFilterChain;
    import org.springframework.security.web.authentication.forcechangepassword.ForceChangePasswordFilter;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        private final ForceChangePasswordFilter forceChangePasswordFilter;
        private final MfaVerificationFilter mfaVerificationFilter;
    
        // 构造函数注入依赖
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                .authorizeHttpRequests(auth -> auth
                    .requestMatchers("/api/auth/login", "/api/auth/change-password", "/api/auth/setup-mfa").permitAll()
                    .requestMatchers("/api/admin/**").hasRole("SUPER_ADMIN")
                    .anyRequest().authenticated()
                )
                .addFilterAfter(forceChangePasswordFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(mfaVerificationFilter, ForceChangePasswordFilter.class)
                .sessionManagement(session -> session
                    .maximumSessions(1)
                    .maxSessionsPreventsLogin(true)
                )
                .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
                .headers(headers -> headers
                    .contentSecurityPolicy("default-src 'self'; script-src 'self'")
                    .and()
                    .frameOptions().deny()
                );
                
            return http.build();
        }
    }

优点:安全性极高,符合审计要求,适合高安全级别的系统

缺点:实施复杂,需要完整的DevOps和密钥管理体系

总结对比

项目规模 推荐方案 核心特点 安全级别 实施复杂度
小型项目 启动时自动创建(带开关) 简单快捷,适合快速部署
中型项目 命令行工具初始化 可审计,流程规范
大型项目 初始化脚本 + 密钥管理 符合安全标准,可追溯 极高

所有方案的共同原则:

  1. 绝不硬编码密码到代码中
  2. 强制首次登录修改密码
  3. 使用强哈希算法存储密码
  4. 记录初始化操作日志
  5. 超级管理员权限最小化
相关推荐
千里码aicood3 小时前
【springboot+vue】党员党建活动管理平台(源码+文档+调试+基础修改+答疑)
java·数据库·spring boot
Chan163 小时前
【智能协同云图库】基于统一接口架构构建多维度分析功能、结合 ECharts 可视化与权限校验实现用户 / 管理员图库统计、通过 SQL 优化与流式处理提升数据
java·spring boot·后端·sql·spring·intellij-idea·echarts
库库林_沙琪马3 小时前
REST接口幂等设计深度解析
spring boot·后端
驾驭人生3 小时前
Asp .Net Core 系列:Asp .Net Core 集成 Hangfire+MySQL
数据库·mysql·.netcore
xhbh6663 小时前
不止是DELETE:MySQL多表关联删除的JOIN语法实战详解
数据库·mysql·程序员·mysql删除语句
知其然亦知其所以然4 小时前
面试官微笑发问:第100万页怎么查?我差点当场沉默…
后端·mysql·面试
蒋星熠5 小时前
深入 Kubernetes:从零到生产的工程实践与原理洞察
人工智能·spring boot·微服务·云原生·容器·架构·kubernetes
ponnylv5 小时前
深入剖析Spring Boot自动配置原理
spring boot·spring
小蒜学长5 小时前
旅行社旅游管理系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·旅游