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. 小型项目(团队≤5人,功能简单)](#1. 小型项目(团队≤5人,功能简单))
- [2. 中型项目(团队5-20人,业务中等复杂度)](#2. 中型项目(团队5-20人,业务中等复杂度))
- 3.大型项目(团队>20人,复杂业务系统)
- 总结对比
实现方式
1. 数据库脚本初始化(最直接的方式)
通过预执行 SQL 脚本创建管理员用户,适合数据库初始化阶段使用。
实现步骤:
- 创建用户表和角色表
- 插入预设角色
- 插入加密后的管理员用户
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 应用完全启动后执行)
实现步骤:
- 定义用户和角色实体类
- 创建数据访问层(Repository)
- 实现初始化逻辑
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. 通过接口手动创建(最安全的方式)
系统部署后,通过一个临时的初始化接口创建管理员用户,适合生产环境。
实现步骤:
- 创建一个初始化接口
- 添加安全限制(如仅允许本地访问)
- 通过接口调用创建管理员
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 数据库版本控制
通过数据库版本控制工具管理初始化脚本,适合团队协作和版本化部署。
实现步骤:
- 添加 Flyway 依赖
- 创建初始化脚本
- 配置 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(配合加密配置)
无论选择哪种方法,都需遵循以下安全原则:
- 密码必须加密存储(推荐 BCrypt 或 Argon2)
- 首次登录后强制修改默认密码
- 最小权限原则(超级管理员仅用于系统初始化)
- 敏感配置(如初始密码)避免硬编码,通过环境变量或配置中心注入
根据项目规模及复杂度选择方式
在 Spring Boot + MySQL 系统中创建第一个超级管理员用户的方案选择,应根据项目规模和复杂度进行调整。以下是针对大、中、小型项目在生产环境中的最佳实践:
1. 小型项目(团队≤5人,功能简单)
推荐方案:应用启动时自动创建(带安全开关)
适合场景:快速部署、维护成本低的小型应用,如内部管理工具、简单的业务系统。
实现步骤:
-
数据库表结构(同上一回答,包含用户、角色和关联表)
-
安全配置类
java@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // 工作因子12,安全性较高 } }
-
带开关的初始化器
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(".*[!@#$%^&*()].*"); } }
-
配置文件
properties# application-prod.properties app.initialize-admin=false # 默认关闭 app.admin.username=admin app.admin.email=admin@example.com # 生产环境通过环境变量设置: export APP_ADMIN_INITIAL_PASSWORD=强密码
-
首次启动命令
bash# 仅首次启动时使用,创建完成后禁用 APP_ADMIN_INITIAL_PASSWORD='StrongP@ssw0rd2024' java -jar app.jar --spring.profiles.active=prod --app.initialize-admin=true
优点:实现简单,部署便捷,适合资源有限的小团队
缺点:安全性中等,需要手动确保初始化后关闭开关
2. 中型项目(团队5-20人,业务中等复杂度)
推荐方案:命令行工具初始化 + 密码重置机制
适合场景:有专职运维人员,需要更规范的部署流程的应用,如企业级SaaS、业务管理系统。
实现步骤:
-
创建专用的命令行工具
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(); } } }
-
服务层实现
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; } // 其他方法... }
-
使用流程
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流程。
实现步骤:
-
数据库设计与迁移策略 :使用
Flyway
或Liquibase
进行版本化数据库迁移,确保表结构创建和初始数据插入可追溯。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;
-
密码生成与管理工具:开发专用工具生成符合 NIST 标准的强密码,并使用 BCrypt 算法 (工作因子 12+) 进行哈希处理。
javaimport 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(); } }
-
创建超级管理员脚本:使用单独的数据库迁移脚本创建超级管理员,密码哈希通过前面的工具生成。
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 );
-
部署流程与安全控制:整合 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")"'" }'
-
首次登录强制安全措施:实现首次登录必须修改密码并配置多因素认证 (MFA)。
javaimport 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); } }
javaimport 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和密钥管理体系
总结对比
项目规模 | 推荐方案 | 核心特点 | 安全级别 | 实施复杂度 |
---|---|---|---|---|
小型项目 | 启动时自动创建(带开关) | 简单快捷,适合快速部署 | 中 | 低 |
中型项目 | 命令行工具初始化 | 可审计,流程规范 | 高 | 中 |
大型项目 | 初始化脚本 + 密钥管理 | 符合安全标准,可追溯 | 极高 | 高 |
所有方案的共同原则:
- 绝不硬编码密码到代码中
- 强制首次登录修改密码
- 使用强哈希算法存储密码
- 记录初始化操作日志
- 超级管理员权限最小化