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. 超级管理员权限最小化
相关推荐
爱考证的小刘19 小时前
MySQL OCP认证、Oracle OCP认证
mysql·oracle·oracle数据库·oracle认证·mysql自学·mysql题库·oracle学习
yjsstar19 小时前
数据库MySQL基础
数据库·mysql
桦020 小时前
[MySQL]数据类型
数据库·mysql
小小的木头人21 小时前
Docker MySQL 单主从及分表函数
mysql
小蜗的房子21 小时前
MySQL学习之SQL语法与操作
数据结构·数据库·经验分享·sql·mysql·学习方法·数据库开发
洲覆21 小时前
MySQL 索引原理
数据库·mysql
摇滚侠1 天前
Spring Boot3零基础教程,函数式 Web 新特性,笔记51
java·spring boot·笔记
执笔论英雄1 天前
【大模型推理】ScheduleBatch 学习
java·spring boot·学习
15Moonlight1 天前
06-MySQL基础查询
数据库·c++·mysql·1024程序员节
nzxzn1 天前
MYSQL第三次作业
数据库·mysql