You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JEE(JSF)迁Spring Boot:数据库未加密密码优雅加密方案咨询

存量明文密码批量加密方案(基于Spring Boot CommandLineRunner)

核心思路

利用Spring Boot的CommandLineRunner Bean,在应用启动时自动检测数据库中未加密的用户密码,通过Spring Security提供的加密器批量加密并更新回数据库,同时加入迁移状态标记避免重复执行,全程无需手动操作。

具体实现

1. 调整用户实体类

新增密码格式判断方法,用于区分明文与加密后的密码(以BCrypt为例,加密后的密码以$2a$/$2b$/$2y$开头):

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    // 其他业务字段...

    // 判断密码是否为明文
    public boolean isPasswordPlainText() {
        return !password.startsWith("$2a$") && !password.startsWith("$2b$") && !password.startsWith("$2y$");
    }

    // getter & setter
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

2. 扩展用户Repository

添加查询所有明文密码用户的方法(支持分页避免内存溢出):

public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT u FROM User u WHERE u.password NOT LIKE '$2a$%' AND u.password NOT LIKE '$2b$%' AND u.password NOT LIKE '$2y$%'")
    Page<User> findAllWithPlainTextPasswords(Pageable pageable);
}

3. 迁移状态标记实体(可选但推荐)

创建专门的迁移状态表,避免每次启动重复执行加密逻辑:

@Entity
@Table(name = "migration_status")
public class MigrationStatus {
    @Id
    private String id; // 用固定值如"password_encryption"作为主键
    private boolean completed;

    // getter & setter
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public boolean isCompleted() {
        return completed;
    }

    public void setCompleted(boolean completed) {
        this.completed = completed;
    }
}

对应的Repository:

public interface MigrationStatusRepository extends JpaRepository<MigrationStatus, String> {
}

4. 实现CommandLineRunner迁移逻辑

@Component
public class PasswordMigrationRunner implements CommandLineRunner {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final MigrationStatusRepository migrationStatusRepository;

    // 构造函数注入(Spring 4.3+自动支持)
    public PasswordMigrationRunner(UserRepository userRepository, 
                                   PasswordEncoder passwordEncoder,
                                   MigrationStatusRepository migrationStatusRepository) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.migrationStatusRepository = migrationStatusRepository;
    }

    @Override
    public void run(String... args) throws Exception {
        // 检查迁移是否已完成
        MigrationStatus status = migrationStatusRepository.findById("password_encryption").orElse(null);
        if (status != null && status.isCompleted()) {
            System.out.println("密码加密迁移已完成,跳过执行");
            return;
        }

        // 分页处理明文密码用户
        int pageSize = 100;
        int pageNum = 0;
        int totalProcessed = 0;
        Page<User> userPage;

        do {
            userPage = userRepository.findAllWithPlainTextPasswords(PageRequest.of(pageNum, pageSize));
            if (userPage.isEmpty()) break;

            userPage.getContent().forEach(user -> {
                String encryptedPwd = passwordEncoder.encode(user.getPassword());
                user.setPassword(encryptedPwd);
            });

            userRepository.saveAll(userPage.getContent());
            totalProcessed += userPage.getNumberOfElements();
            pageNum++;
        } while (userPage.hasNext());

        // 标记迁移完成
        if (status == null) {
            status = new MigrationStatus();
            status.setId("password_encryption");
        }
        status.setCompleted(true);
        migrationStatusRepository.save(status);

        System.out.println("密码加密迁移完成,共处理" + totalProcessed + "个用户");
    }
}

5. 配置Spring Security加密器

@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 使用Spring Security推荐的BCrypt强哈希加密器
        return new BCryptPasswordEncoder();
    }

    // 其他Spring Security认证配置(如UserDetailsService、AuthenticationManager等)
}

关键注意事项

  • 数据库备份:执行前务必备份用户表,避免数据异常丢失。
  • 环境测试:先在测试环境验证加密逻辑,确保加密后用户能正常登录。
  • 算法适配:若旧系统使用特定加密算法(如MD5),需替换对应的PasswordEncoder(如MessageDigestPasswordEncoder("MD5")),但推荐优先升级到BCrypt等强算法。
  • 性能优化:用户量较大时,通过分页查询分批次处理,避免一次性加载大量数据占用内存。

内容的提问来源于stack exchange,提问作者azerELweed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 04:45:34