Druid ConfigTools实战:数据库密码非对称加密,告别明文配置
发布时间:2026/10/18 18:16:00 锦皓数字建站

你有没有在项目的配置文件里见过这样一行password: 123456我见过太多次。多数团队把数据库密码晒在配置仓库的阳光下等到某天发现 Git 历史里出现一串不该出现的连接串才开始后悔。今天聊的解决办法不复杂使用 Druid 连接池自带的 ConfigTools 能力对数据库密码做非对称加密把明文密码从配置文件里彻底请出去。这个方案不需要引入额外框架适合已经在用 Druid 连接池、尤其是 Spring Boot 项目并且每天要跟数据库配置打交道的后端同学。读完这篇你可以独立完成密钥生成、配置改造、启动验证和后续维护也能顺手把团队配置管理里的安全漏洞补上。1. 为什么要给数据库密码加密以及 Druid 方案的取舍1.1 明文密码躺在配置里的真实风险在聊技术实现之前先说一个场景。很多项目的application.yml是直接放在 Git 仓库里的甚至还有把多个环境的连接信息写在一个文件里的情况。只要仓库权限管理稍微松一点任何能读取代码的人都能拿到生产数据库的账号密码。这不是假设我见过不止一次因为配置仓库泄露导致数据库被频繁登录的事件。明文密码的风险不只是仓库泄露还有更隐蔽的三条渠道日志与排障过程应用启动失败时有人为了排查问题会把配置文件完整复制到聊天工具里密码就在里面传播。员工交接与离职数据库连接配置通常存在于老员工笔记、本地历史命令、甚至 Docker 启动脚本里换人之后不知道密码被谁看过。审计合规压力现在很多业务要过安全审计数据库账号密码明文存储在配置库这一项基本是必查问题。密码加密并不是为了防住一个绝对技术高超的攻击者而是把“拿到配置文件就能连数据库”这件事的难度提高一个量级。哪怕攻击者拿到了密文和公钥也还原不出真实密码。1.2 常见加密方案对比现在做数据库密码加密可选的路子其实不少但各有代价方案加解密原理优点缺点环境变量存放不加密只把明文移出配置文件简单、灵活环境变量泄露等于密码泄露没有解决根本保护问题配置中心动态管理密文或明文由配置中心下发支持动态刷新集中管理配置中心本身变成单点信任仍需要处理加解密流程自定义加解密工具类自己写一套脱敏/还原逻辑可控性强需要自己维护密钥容易出现“固定密钥写死在代码里”的笑话专业加密库方案启动时用密钥库解密配置属性成熟、功能多引入额外依赖配置复杂度高团队需要一定学习成本Druid ConfigTools非对称加密运行期自动解密零依赖增量Druid 自带只需配公钥和密文仅适用于 Druid 连接池场景如果项目里已经用了 Druid 作为连接池那选 Druid ConfigTools 是性价比最高的。它不需要另起炉灶不增加新的 jar 包ConfigTools 就在 druid 主包里不要求你去理解复杂的密钥库规范生成密钥和密文也就是几条命令的事情。1.3 Druid 的加密解密链路设计Druid 的这套机制底层是非对称加密。用生活类比来说私钥就像一枚刻章密文是盖过章的凭证公钥则是一台专门验章的机器。只有拿着刻章的人能制造凭证但任何人只要拿到验章机都能确认凭证内容是否真实有效。流程拆开是这样的开发或运维人员使用 ConfigTools 生成密钥对一个私钥一个公钥。私钥用来加密数据库明文密码产出密文。配置文件中不再写明文而是写这段密文。Druid 连接池启动时ConfigFilter 过滤器读取config.decrypttrue和config.decrypt.key也就是公钥用公钥把密文还原成明文密码。还原后的明文密码被用来建立真实数据库连接之后的连接池行为跟普通配置完全一致。关键点在于应用运行期只需要公钥不需要私钥。公钥本身是公开信息就算被人拿走也无法反向推出数据库密码。私钥只在生成密文的环节出现一次可以离线保存从源头上减少了泄露面。2. 实操五分钟跑通 Druid 数据库密码加密2.1 引入依赖时的版本选择如果项目是 Spring Boot通常直接使用集成好的 starter 即可。在pom.xml里引入依赖dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.20/version /dependency版本这里我多说一句不要拿一个太老的 1.1.x 版本硬套在 Spring Boot 2.4 以上的项目里配置绑定机制换过好几轮老版本会出现“明明配了却不生效”的诡异问题。建议直接使用 1.2.x 较新的版本并且让 druid 主版本在项目内保持一致避免同时存在多个 druid jar。如果项目没有使用 Spring Boot也不想引入 starter只引入druid主包也可以dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency2.2 使用 ConfigTools 生成密钥对和密文Druid 的 ConfigTools 提供了命令行模式直接用 jar 包就能生成不需要写代码。先确认本机 Maven 仓库里有对应版本的 druid jar然后执行java -cp ~/.m2/repository/com/alibaba/druid/1.2.20/druid-1.2.20.jar com.alibaba.druid.filter.config.ConfigTools 你的数据库密码注意如果你是在 Windows 上的 PowerShell 或者命令行里执行路径分隔符要换成\且密码中的特殊字符最好用英文双引号包住防止 shell 截断。执行完成后终端会输出三行关键内容privateKey: MIIEvQIBADANBg...私钥 publicKey: MIIBIjANBgk...公钥 password: B7nQa0SN5elq...密文这三行值各自的用途privateKey私钥。只在生成密文的时候使用生成完就要妥善保管绝对不能进配置文件、不能进 Git。publicKey公钥。填到应用配置里告诉 Druid 用什么来解密。password加密后的密文。替换掉原来application.yml里的明文密码。如果你不方便执行命令行也可以临时写一个 main 方法调用 ConfigTools 生成public class PasswordGenerator { public static void main(String[] args) throws Exception { String[] pair com.alibaba.druid.filter.config.ConfigTools.genKeyPair(); String privateKey pair[0]; String publicKey pair[1]; String encryptedPwd com.alibaba.druid.filter.config.ConfigTools.encrypt(privateKey, 你的数据库密码); System.out.println(privateKey: privateKey); System.out.println(publicKey: publicKey); System.out.println(password: encryptedPwd); } }跑完之后记得把临时类删掉不要留在项目源码里。命令行方式生成时还建议确认终端历史记录不会把带密码的命令同步到团队共享环境。2.3 修改配置文件三个关键项一个都不能少拿到密文和公钥之后修改application.yml。下面是一份完整的配置示例假设数据源配置在spring.datasource.druid前缀下这种方式在较新的 druid starter 里最稳妥spring: datasource: druid: url: jdbc:mysql://localhost:3306/your_db?useUnicodetruecharacterEncodingutf8 username: your_db_user password: B7nQa0SN5elq... driver-class-name: com.mysql.cj.jdbc.Driver filter: config: enabled: true connection-properties: config.decrypttrue;config.decrypt.keyMIIBIjANBg...公钥内容...这里有三个关键配置项缺一不可password从明文替换成加密后的密文。filter.config.enabled: true告诉 Druid 启用 ConfigFilter 过滤器让它有机会对密码进行解密。connection-properties把解密开关和公钥以连接属性的方式传给 ConfigFilter。config.decrypttrue表示需要解密config.decrypt.key就是公钥本体。如果你用的是spring.datasource.url这种不带druid前缀的写法也可以在 Spring Boot 的自动装配场景下生效但需要注意不同版本的 starter 读取前缀的差别。我的建议是干脆统一写在spring.datasource.druid下一次性把 url、username、password 都交给 Druid 管理减少阴间问题。密文和公钥都是很长的一串 Base64 字符YAML 中建议用双引号包起来。虽然 Base64 里没有冒号、井号这类高危险字符但双引号能避免某些解析器在长字符串上做奇怪截断。2.4 启动验证怎么确认真的生效了配置改完启动应用正常会在启动日志里看到 Druid 连接池初始化的信息。如果直接连数据库成功且接口能正常查询数据说明解密链路是通的。但我还建议你做一次主动验证把password这一项的值随便改掉一位然后重新启动。这时候会有两种情况如果配置链路生效Druid 会用公钥去解这段错误密文大概率直接抛出解密失败的异常。如果看到的是连接数据库时Access denied for user ... (using password: YES)说明密码已经成功解密只是解出来的值不对这时候要去检查“密文到底是用哪把私钥加密的”。这两种报错指向的问题完全不同提前分辨清楚后续排障会快很多。2.5 多数据源与原生配置姿势多数据源场景稍微复杂一点但原理是一样的每个数据源都需要有自己的filter.config.enabled和connection-properties。如果你在 Spring Boot 里用ConfigurationProperties扩展了多个数据源每一组 Druid 配置都要把解密参数补齐。公钥可以共用一份因为多套环境的公钥并不冲突但建议按环境隔离下一章会细说。如果不使用 starter而是手动创建DruidDataSource配置方式稍有不同。核心是给 dataSource 设置connectionProperties并显式指定过滤器DruidDataSource dataSource new DruidDataSource(); dataSource.setUrl(jdbc:mysql://localhost:3306/your_db); dataSource.setUsername(your_db_user); dataSource.setPassword(B7nQa0SN5elq...); dataSource.setConnectionProperties(config.decrypttrue;config.decrypt.keyMIIBIjANBg...); dataSource.setFilters(config,stat,wall);注意这里的setFilters(config,stat,wall)如果只写statConfigFilter 不会启用密文会被当成真正的密码去连接数据库必然失败。3. 核心参数解析与密钥管理经验3.1 config.decrypt 和 config.decrypt.key 的作用边界很多人配完之后只记住了“照抄就行”一旦报错就懵了。所以我把这两个参数的作用说得再透一点。config.decrypttrue是给 ConfigFilter 的一个信号告诉它当前数据源连接属性里藏着需要解密的字段。config.decrypt.key则是解密时要用到的公钥。两者是配合关系只写config.decrypttrue不写 key过滤器不知道用哪把“钥匙”去解只写 key 不写config.decrypttrue过滤器默认不干活。这里有一个很容易踩到的边界问题connection-properties里的参数最终会成为 Druid 建立连接前的属性集合ConfigFilter 在连接创建之前拦截并处理。如果项目里存在多个过滤器且自定义过滤器把连接属性提前消费掉了ConfigFilter 可能拿不到完整信息。最常见的情况就是别人新增了一个自定义Filter把connectionProperties里的参数清空或者覆盖了导致解密配置不生效。3.2 为什么是非对称而不是“写死一个密钥做 AES”不少人看到这套机制会问直接用同一个密钥加密解密不是更简单吗确实对称加密在实现上更直观但有一个致命问题如果用来加密的密钥和生产环境用来解密的密钥是同一个那这个密钥就必然要出现在应用侧配置或代码里否则无法运行。一旦攻击者拿到配置和密钥加密形同虚设。非对称加密的价值在于“加密用的私钥”和“解密用的公钥”可以分开放。公钥随便给人看私钥锁在保险柜里。配置文件、Git 仓库、应用服务器上只有密文和公钥就算全部泄露也拿不到数据库明文密码。这也是我为什么不推荐自己写一套 AES 工具类来做这件事的原因——密钥管理很容易失控一旦密钥被写进代码还不如不加密。3.3 公钥、私钥、密文的保管边界这是整个加密方案里最关键的运维红线。我遇到过团队把生成的密钥对直接写进 README 的也有把私钥放进 Git 仓库的这种操作等于白加了一层壳。内容能否提交代码仓库原因密文password可以密文依赖私钥才能还原仓库泄露密度相对低公钥publicKey可以公钥公开也无妨无法通过公钥反推私钥私钥privateKey绝对不能拿到私钥的人可以自己加密任意密码再配合公钥还原等于完全绕过保护数据库明文密码绝对不能明文一旦进仓库之前的加密改造全部白费私钥建议保存在团队的密码管理工具里或者由负责数据库安全的管理员离线保管。生成密文的过程最好在本地开发机或者专用运维机上完成不要在 CI 构建机上生成避免密钥流经构建日志。3.4 多环境密钥规划与轮换方案开发环境、测试环境、生产环境如果共用同一个密钥对那一旦开发环境私钥泄露生产库密码也跟着暴露。我见过有团队为了图省事把所有环境的数据库密码都用同一把私钥生成。这种做法风险很大等于把生产安全押在开发环境的管理水平上。建议每套独立环境生成独立的密钥对至少要做到生产环境单独一套。配合 Spring Boot 的多 profile 配置可以这样设计# application-prod.yml spring: datasource: druid: connection-properties: config.decrypttrue;config.decrypt.key${DB_PUBLIC_KEY}公钥通过环境变量DB_PUBLIC_KEY注入不写在代码仓库中。代码仓库里只保留密文生产部署平台通过密钥管理功能动态注入公钥这样即使仓库完全公开生产环境仍然无法被直接连接。密钥轮换的情况通常是数据库密码变更或私钥疑似泄露。轮换步骤建议按这个顺序来生成新的密钥对。用新私钥加密新的数据库密码或不变的密码。把新密文写入配置文件新公钥更新到配置中心或环境变量。先让一个节点滚动更新并验证确认连接正常后再全量发布。确认稳定后销毁旧的私钥和旧的公钥配置。4. 常见问题与排查技巧实录4.1 高频报错速查表下面这张表是我在团队项目和社区交流里整理出来的高频问题按“现象 → 原因 → 解法”整理排障时直接对照可以省很多时间。报错现象可能原因排查方向和解决启动报decrypt failed公钥与密文不匹配公钥字符串被空格或换行污染确认密文是由同一把私钥生成的检查config.decrypt.key是否完整复制去掉首尾空格启动报Access denied for user ...解密流程正常但解密后的密码不对检查原始明文中途是否被 shell 截断重新用正确密码生成密文启动正常但查询时报连接失败connection-properties没被 ConfigFilter 读取到自定义 Filter 覆盖了属性显式在配置中启用filter.config.enabled或setFilters(config,...)检查是否存在自定义过滤器篡改属性配置了密文但连接时仍用明文配置文件前缀写错Spring Boot 根本没有把配置绑定到 DruidDataSource统一使用spring.datasource.druid.*前缀查看启动日志确认数据源属性yml 启动就报格式错误密文或公钥没有加引号YAML 解析出现意外将密文和公钥用英文双引号包裹本地环境正常生产环境报解密失败生产公钥来自另一套密钥环境变量未注入分别生成各环境密钥检查生产环境DB_PUBLIC_KEY是否为空或错误使用高版本 JDK 报 RSA 解密相关异常部分旧版本 druid 与 JDK 安全策略存在兼容问题升级 druid 版本或更换 JDK 小版本4.2 三个真实踩坑案例复盘第一个坑私钥跟着 Git 历史走。某次给团队做安全整改时发现生成密钥对的代码被提交到了仓库私钥以字符串形式躺在工具类里。虽然当时没有真实攻击发生但整个加密体系已经失效。整改方案是重新生成密钥对、更新所有密文配置然后把旧提交从仓库历史里清理掉并在团队规范里规定私钥严禁入库。这件事告诉我们技术方案做得再周全密钥管理流程跟不上等于零。第二个坑配置中心的公钥占位符被解析成空字符串。某个项目把公钥放到配置中心管理配置项写的是${DB_PUBLIC_KEY}但部署平台没有配置这个变量导致启动时连接属性变成config.decrypttrue;config.decrypt.keyConfigFilter 拿到空公钥直接抛解密失败。排查了半天最后发现是环境变量拼写不一致。解决办法很简单在应用启动脚本里加了一个显式校验如果公钥为空直接启动失败并输出清晰提示避免以半明半密的状态跑起来。第三个坑生成密文时特殊字符被 shell 处理。当时数据库管理员给了一个临时密码里面有#和$符号。执行命令时没有给密码加引号shell 把后半段当成注释或变量解析生成的密文对应的并不是完整密码。启动时解密成功但数据库登录一直被拒绝。这个问题的教训是命令行生成密文时任何包含特殊字符的密码都要用双引号整体包住对于生产密码建议直接把密码写入临时环境变量再引用避免出现在终端历史里。4.3 密钥搞坏之后如何无损恢复如果私钥丢了或者公钥配错了数据库里的数据并不会丢受影响的是应用启动链路。最稳妥的处理方式是分情况操作公钥配错只需要用正确公钥更新配置不需要重新生成密文。密文与公钥不匹配需要拿到原始数据库密码用私钥原样加密生成新密文。如果原私钥还能找到这个过程在本地一分钟完成。私钥完全丢失这时候最安全的做法不只是重新生成密钥对还应该考虑同时轮换数据库密码。因为私钥丢了不代表密码一定泄露但从安全角度看已经脱离了“可控”状态。建议 DBA 配合改一次密码再用新私钥加密新密码。无论哪种情况都建议在配置中心保留一个可以快速回滚的历史版本。发布时先灰度一台实例验证通过再全量推避免出现“上线即全瘫”的场面。5. 安全加固与影响范围分析5.1 这一改动影响了哪些模块和流程集成 Druid 密码加密表面上看只是改了一行配置实际上会给周边流程带来一系列变化。连接池初始化环节是最直接受影响的。Druid 在启动阶段多了一步解密动作虽然耗时通常可以忽略不计但如果配置错误应用启动会直接失败所以这个改动必须走完整的发布验证流程。配置管理侧的变化更明显从今以后项目仓库里不允许出现任何数据库明文密码生产环境密码由 DBA 或指定负责人统一管理和生成开发同学如果需要本地连库也要走同样的加密流程而不是图省事直接复制明文到本地配置。CI/CD 流水线也需要同步关注构建机上如果跑过生成密钥的命令要确认私钥没有进入构建产物或日志发布脚本不能把数据库明文密码作为参数传入。对 DBA 来说影响是正向的数据库账号密码不再散落在各个开发者本地密码变更后可以更可控地推动配置刷新。真正受影响的角色是那些手里拥有明文密码的人他们需要把密码迁移到安全的密钥管理环境中。5.2 结合配置中心与 CI/CD 的落地建议我比较推荐的生产落地方式是代码仓库只保存密文公钥通过配置中心或环境变量注入。以 Spring Boot 为例在application.yml里用占位符spring: datasource: druid: connection-properties: config.decrypttrue;config.decrypt.key${DB_PUBLIC_KEY}这样即使有人把仓库打包下载他也只看到一段密文和一个变量引用无法直接连库。配置中心里可以按环境分别设置开发环境公钥放在开发配置中心群组。生产环境公钥放在生产配置中心群组且只有指定运维人员有修改权限。CI/CD 侧还要增加一个探活动作比如发布后自动执行一个简单的数据库查询接口验证解密链路是否正常。如果连接失败立即回滚配置。这一步很便宜但能避免“发布到一半才发现密码配错”的尴尬。5.3 还要做哪些配套加固数据库密码加密只是数据库安全里的一环别把它当成万能药。配套的加固至少还要做这几件Druid 监控接口不要暴露到公网。如果开启了StatViewServlet一定要配置登录账号密码和 IP 白名单很多泄露事故就是从监控后台撞进去的。数据库账号权限最小化。应用账号只保留需要的增删改查权限不要把 DBA 权限给到应用连接账号这样即使连接信息泄露损失也能控制在一定范围内。配置文件的文件权限要收紧。生产服务器上的application.yml不应该对所有人可读建议由运维账号管理。定期检查 Git 历史中是否还残留明文密码。如果有不要只删除当前文件要处理历史提交记录并触发一次密钥轮换。把加密机制和这些配套措施放在一起看整个数据库凭据保护才算真正闭环。代码仓库里看到的不再是明晃晃的密码而是经过非对称加密之后的密文以及一把完全不敏感的公钥。按我个人经验Druid 这套加密方案最大的价值不是算法本身有多强而是它把安全习惯嵌入了日常开发流程。第一次配置可能花二十分钟之后每次新建环境都顺手做一遍就不会再出现配置仓库里躺着一堆明文密码的尴尬。最后再补充一个容易被忽略的小技巧生成密钥的时候顺手把时间、环境、负责人和私钥存放位置记到团队内部清单里半年后轮换时会省非常多事。坚持用下来数据库密码这扇门会扎实很多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。