第一部分我们为啥要对数据库加密

兄弟们,你们有没有在 application.yml 文件里直接写过数据库密码?是不是每次提交代码到 Git 仓库时,心里都咯噔一下?

就像这样

当然如果数据库在本机不存在泄露问题,但是如果你是部署在服务器上的数据库就会出现以下问题

  • 安全风险:一旦代码仓库(如 GitHub)权限泄露,数据库就等于“裸奔”。

  • 运维困难:生产环境、测试环境、开发环境密码都不同,每次修改密码都要改代码、重新打包上线,非常繁琐。

  • 审计与合规:在很多公司,安全审计是明令禁止配置文件中出现明文密码的。

第二部分:我们如何做

1. 方案概览

  • 配置中心:如 Nacos、Apollo,把密码放在配置中心统一管理(这是大厂主流方案)。

  • 环境变量:通过启动脚本传入密码(简单直接,但管理稍乱)。

  • 本地加密:在配置文件中使用密文,在程序启动时自动解密。这对于中小型项目来说,是一个成本最低、见效最快的方案。

2. 主角登场:Druid 加密

  • 我们今天的主角就是 druid-spring-boot-starter 自带的加密功能。

  • 优点:无需引入新的依赖,如果你已经在使用 Druid 连接池,那这个功能就是“白送”的,非常方便。

第三部分:实战演练——手把手带你飞(核心内容,上代码)

  • 1. 编写密钥生成和加解密工具

    • 贴出我们的 DruidEncryptUtil.java 类的代码。

    • public class DruidEncryptUtil {
          /**
           * 私钥 - 用于加密操作
           */
          public static String privateKey;
      
          /**
           * 公钥 - 用于解密操作
           */
          public static String publicKey;
      
          /**
           * 静态代码块用于初始化密钥对
           * 使用ConfigTools生成512位的RSA密钥对
           * 注意:实际生产环境应考虑更安全的密钥存储方式
           */
          static {
              try {
                  String[] keyPair = ConfigTools.genKeyPair(512);
                  privateKey = keyPair[0];
                  System.out.println("privatekey:" + privateKey);
                  publicKey = keyPair[1];
                  System.out.println("publicKey:" + publicKey);
              } catch (NoSuchAlgorithmException e) {
                  e.printStackTrace();
              } catch (NoSuchProviderException e) {
                  e.printStackTrace();
              }
          }
      
          /**
           * 使用私钥对明文进行加密
           *
           * @param plainText 明文内容
           * @return 加密后的字符串
           * @throws Exception 加密过程中的异常
           */
          public static String encrypt(String plainText) throws Exception {
              String encrypt = ConfigTools.encrypt(privateKey, plainText);
              System.out.println("encrypt:" + encrypt);
              return encrypt;
          }
      
          /**
           * 使用公钥对密文进行解密
           *
           * @param encryptText 加密后的字符串
           * @return 解密后的明文
           * @throws Exception 解密过程中的异常
           */
          public static String decrypt(String encryptText) throws Exception {
              String decrypt = ConfigTools.decrypt(publicKey, encryptText);
              System.out.println("decrypt:" + decrypt);
              return decrypt;
          }
      
          /**
           * 主方法 - 测试加密解密功能
           *
           * @param args 命令行参数(未使用)
           * @throws Exception 测试过程中可能抛出的异常
           */
          public static void main(String[] args) throws Exception {
              String encrypt = encrypt("123456");
              System.out.println("encrypt:" + encrypt);
              decrypt(encrypt);
          }
      }

    • 重点讲解这个工具类作用就是,生成一对秘钥和公钥,一个加密方法,一个解密方法(用于验证是否可以成功解密),当你运行后输出了123456说明这个工具类没有问题

    • 重点讲解与踩坑反思:一个差点“骗”了我的工具类

      拿到这个工具类,我的第一反应是它的 static 代码块有问题,因为它每次启动都会生成不同的密钥对。我当时心想:“这肯定会导致项目拿着旧公钥解不开新密码,启动必报错!”

      然而,诡异的事情发生了——项目启动和业务调用一切正常!

      经过排查才恍然大悟:这个 DruidEncryptUtil.java 只是一个离线的“密钥生成工具”,Spring Boot 应用在运行时根本不会调用它。应用的解密行为,完全依赖于 application.yml 中那对固定的公钥和密文。

      但这反而更危险! 如果一个新同事想用这个工具生成新密码,他很可能会忘记更新 yml 里的公钥,从而导致生产事故。

      正确做法

    • 运行一次 main 方法,保存好控制台输出的 privateKeypublicKey

    • 改造 DruidEncryptUtil.java,删除 static 代码块,将保存好的密钥作为 final 常量写死,让它变成一个行为固定的安全工具。

    • decrypt 方法在这里的最大作用,就是生成密钥后,当场验证这对密钥是否工作正常。

    • 修改后的类

      public class DruidEncryptUtil {
          /**
           * 私钥 - 用于加密操作
           */
          public static String privateKey = "MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAqxmxNwOc9IoS6MVVZ3ZnWpiP2agHcObaeUR+8jVyWMnivn21teh/+QvbPrDeEaY3i5DdarM0egK4i++Rp2HgawIDAQABAkAK1JXnhsg5B0LQwfaSYmfpNisBrgv6iFv5ie2dmBoXpv7xdVK3TmxssBgbyWSvY3SaR9K3/Fqn7eeO+5QFFx3xAiEAzc94Eks5FWGOUwHqJ54cVQAwiwGbkN1hFiy0fwVGJi0CIQDU009VrAcvdC9CPYXWkQ1g2F1fSGTDKlu190PGGHkX9wIgObHjcx1rTzcd8t8iiSClyJ5Y/V7iAWZOBS1bHBCabbECIDDx5+zsAzsGnVe+jmkqMsly+QZQv9uigjT3CL8mIbNBAiBeuqLo+94je9ZXKxPNQeFXI1xtfUhoHmv/XfgXvZjSnA==";
      
      
          /**
           * 公钥 - 用于解密操作
           */
          public static String publicKey = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKsZsTcDnPSKEujFVWd2Z1qYj9moB3Dm2nlEfvI1cljJ4r59tbXof/kL2z6w3hGmN4uQ3WqzNHoCuIvvkadh4GsCAwEAAQ==";
      
      
      
          /**
           * 使用私钥对明文进行加密
           *
           * @param plainText 明文内容
           * @return 加密后的字符串
           * @throws Exception 加密过程中的异常
           */
          public static String encrypt(String plainText) throws Exception {
              String encrypt = ConfigTools.encrypt(privateKey, plainText);
              System.out.println("encrypt:" + encrypt);
              return encrypt;
          }
      
          /**
           * 使用公钥对密文进行解密
           *
           * @param encryptText 加密后的字符串
           * @return 解密后的明文
           * @throws Exception 解密过程中的异常
           */
          public static String decrypt(String encryptText) throws Exception {
              String decrypt = ConfigTools.decrypt(publicKey, encryptText);
              System.out.println("decrypt:" + decrypt);
              return decrypt;
          }
      
          /**
           * 主方法 - 测试加密解密功能
           *
           * @param args 命令行参数(未使用)
           * @throws Exception 测试过程中可能抛出的异常
           */
          public static void main(String[] args) throws Exception {
              String encrypt = encrypt("123456");
              System.out.println("encrypt:" + encrypt);
              decrypt(encrypt);
          }
      }

  • 2.配置 application.yml: 

application.yml 的配置。

server:
  port: 3000
publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKsZsTcDnPSKEujFVWd2Z1qYj9moB3Dm2nlEfvI1cljJ4r59tbXof/kL2z6w3hGmN4uQ3WqzNHoCuIvvkadh4GsCAwEAAQ==
spring:
  main:
    allow-circular-references: true
  datasource:
    username: root
    password: JW/fl0/KyOZsUs/iX3vdCtO83ETGuQ3P5gpCG0Z8SU5bU7iCDbwY07X/ySu63Qd75FYU8AeyhYCYPHcoKkn56w==
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/jc-club?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
    druid:
      type: com.alibaba.druid.pool.DruidDataSource  # <--- 请添加这一行,这是最关键的一步
      initial-size: 20
      min-idle: 20
      connection-properties: config.decrypt=true;config.decrypt.key=${publicKey};
      max-active: 100
      max-wait: 60000
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        login-username: admin
        login-password: 123456
      filter:
        stat:
          enabled: true
          slow-sql-millis: 2000
          log-slow-sql: true
        wall:
          enabled: true
        config:
          enabled: true
logging:
  config: classpath:log4j2-spring.xml

逐行解释(解密五人组)

  1. type: com.alibaba.druid.pool.DruidDataSource: 这是入场券。告诉 Spring Boot:“我要用 Druid,请让它上场!”
  2. filter.config.enabled: true: 这是执行官。告诉 Druid:“把你的加密解密专家 ConfigFilter 派出来干活!”
  3. connection-properties: ...: 这是操作指令ConfigFilter 拿到剧本,上面写着:“你需要解密,钥匙在……”
  4. publicKey: ...: 这是钥匙ConfigFilter 按照指令,拿到了这把公钥。
  5. password: ...: 这是上了锁的保险箱ConfigFilter 最终用钥匙打开了它。

 

第四部分:进阶思考——如何做得更好?(展现深度)

  • 1. 密钥的存放

    • 直接把公私钥写在代码里虽然方便,但在严格的生产环境中还不够安全。

    • 可以建议读者将密钥存储在环境变量服务器的外部配置文件或者专业的**密钥管理服务(KMS)**中,程序启动时去读取。

  • 2. 其他工具 Jasypt

    • 可以简单提一下另一个流行的加密工具 Jasypt

    • 简单对比一下它和 Druid 加密的区别(比如 Jasypt 使用的是对称加密,只需要一个“盐”,配置更简单,但 Druid 的非对称加密理论上更安全)。

第五部分:总结

  • 我们明白了为什么不能在配置文件中存明文密码。

  • 我们学会了如何利用 Druid 自带的功能,零成本地实现密码加密。

  • 最后,我们探讨了在生产环境中更安全的密钥管理方式。

Logo

一站式 AI 云服务平台

更多推荐