ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot数据库密码加密实战:Jasypt与Druid配置详解

Spring Boot数据库密码加密实战:Jasypt与Druid配置详解 明文密码之痛一个真实的项目抢救现场如果你接手过任何一个运行了一年以上的Java项目我敢打赌你见过这样的application.ymlspring: datasource: username: root password: root123是不是很眼熟我刚入行那会儿也觉得这没什么直到有一次客户做安全审计对方的安全工程师打开配置文件指着那个明文密码问我这个数据库是不是能直连这台服务器是不是谁都能登录当时的尴尬我到现在都记得。更现实的问题是数据库密码一旦泄露可能意味着整个业务数据裸奔而最容易被拖出来背锅的往往是最后接触代码的那个人。我写这篇文章的目的很简单——把我自己踩过的坑、试过的方法、最终沉淀下来的方案讲清楚。这篇内容主要面向有一定Java基础、想摆脱把密码写在配置文件里的工程师也适合那些项目刚起步、想一开始就把安全底线立起来的团队。核心思路是在不改变现有代码结构、不引入复杂中间件的前提下把配置文件里的敏感信息变成就算泄露了也无法直接使用的状态。1. 解决方案的横纵对比既然要给密码加密先把思路理清楚。我在选型的时候会从两个维度考虑一是加密能覆盖多大范围二是日常使用的成本高不高。没有完美的方案关键是知道自己项目适合哪一种。方案加密范围侵入性密钥管理难度适合场景Jasypt Spring Boot Starter配置文件中的任意字符串低只需加依赖和改配置中需要外部注入密钥大多数Spring Boot项目Druid ConfigFilter数据库连接密码极低配置即可中公私钥需要妥善保管使用Druid连接池的项目配置中心Nacos/Apollo配置整体托管中需引入配置中心中高微服务架构、多环境管理云厂商KMS所有敏感配置高需深度集成低云平台托管已上云的项目如果项目用了Druid作为连接池直接在Druid层面解决密码加密最省事如果项目用了若依这类基于Spring Boot的快速开发框架通常本身已经集成了Druid那么Druid ConfigFilter也是首选。但如果项目对加密的诉求不只是数据库密码还想把Redis密码、第三方接口密钥等一网打尽Jasypt更合适——它能对配置文件里任何${ENCRYPTED(...)}形式的字符串做透明解密。说实话Jasypt和Druid并不冲突两个一起用也完全可以。Jasypt负责全局的敏感字段Druid负责数据库层的额外防护形成纵深防御。不过在讲实操之前我想先说清楚一个原则问题配置文件密码加密的核心目标不是防黑客而是防泄露后的横向扩散。因为加密后的密码依然可以被解密只是解密的钥匙在外部所以密钥的管理比加密本身更重要。这是我后面反复强调的重点。2. Jasypt实战给Spring Boot配置文件加一道锁2.1 依赖引入与版本选择Jasypt对接Spring Boot的方式有两种一种是旧版的jasypt-spring-boot-starter一种是官方推荐的jasypt-spring-boot-starter注意3.x版本对Spring Boot版本有要求。我用的是Spring Boot 2.7.x项目选的版本是3.0.5表现很稳定。dependency groupIdcom.github.ulisesbocco/groupId artifactIdjasypt-spring-boot-starter/artifactId version3.0.5/version /dependency如果你用的是Spring Boot 3.x需要选4.x版本因为底层适配逻辑改了不少。这里有一个很容易踩的坑jasypt 3.x默认的加密算法是PBEWITHHMACSHA512ANDAES_256它受Java JDK的加密策略限制。如果你的JDK没有放开无限强度加密策略启动的时候会直接报InvalidKeyException: Illegal key size。JDK 8之后的版本一般默认支持但如果你用的是某些精简版JDK这个错误就会冒出来。解决办法很简单升级JDK或者手动替换$JAVA_HOME/jre/lib/security/下的local_policy.jar和US_export_policy.jar。2.2 用命令行生成你的第一段密文Jasypt提供了一个命令行工具可以用jar包直接运行。我从Maven仓库找到对应的jasypt-1.9.3.jar注意这个版本要和starter内部的jasypt版本一致然后执行java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI \ inputMyDocker2024 \ passwordMySecretKey \ algorithmPBEWITHHMACSHA512ANDAES_256输出结果大概是----ENVIRONMENT----------------- Runtime: Oracle Corporation Java 1.8.0_291 ----ARGUMENTS------------------- input: MyDocker2024 password: MySecretKey algorithm: PBEWITHHMACSHA512ANDAES_256 ----OUTPUT---------------------- vFcWl2XTgD9T8gZJDVhSnZi7T6GJvQ这一段输出里的vFcWl2...就是加密后的密文。把它填到配置文件里用ENC(...)包裹spring: datasource: username: root password: ENC(vFcWl2XTgD9T8gZJDVhSnZi7T6GJvQ)然后配置jasypt的密钥和算法jasypt: encryptor: password: ${JASYPT_PASSWORD} algorithm: PBEWITHHMACSHA512ANDAES_256密钥不要直接写在配置文件里这等于把保险柜钥匙放在保险柜上。我一般在启动命令里通过环境变量注入export JASYPT_PASSWORDMySecretKey java -jar my-app.jar或者在IDEA运行配置里添加环境变量JASYPT_PASSWORDMySecretKey。只要能保证配置文件和密钥分离安全性就是合格的。这里再补充一个细节如果jasypt在启动时找不到密钥它不会立刻报错而是在第一次访问加密配置项时才抛出解密异常排查起来会比较困惑。所以启动时最好加一行日志确认配置没问题。2.3 自定义加密器突破默认算法的限制有些老项目用的是JDK 7或者受限制的JDK 8默认的AES-256算法跑不起来。这时候可以自己实现一个加密器fallback到简单一点的算法。写法也很简单Configuration public class JasyptConfig { Bean(jasyptStringEncryptor) public StringEncryptor stringEncryptor() { StandardPBEStringEncryptor encryptor new StandardPBEStringEncryptor(); encryptor.setPassword(System.getenv(JASYPT_PASSWORD)); encryptor.setAlgorithm(PBEWithMD5AndDES); return encryptor; } }注意两个细节第一Bean名称必须是jasyptStringEncryptor否则Spring Boot自动配置会用自己的默认加密器第二这个自定义Bean会让配置文件里的jasypt.encryptor配置失效所以算法和密钥都在代码里设置。这种方式的好处是不用管JCE无限强度策略的问题坏处是PBEWithMD5AndDES不如AES-256安全。我的建议是只要能支持AES-256就别退回到MD5AndDES实在没办法再退回。这里还有一个生成密文时的坑如果你自定义了加密器命令行生成密文时也必须要用同样的算法和密钥不能一个用默认算法、一个用自定义算法否则解密必然失败。3. Druid ConfigFilter实操若依框架集成密码加密3.1 为什么单独把Druid拎出来说很多热门的快速开发框架都是基于Spring Boot Druid搭建的比如若依。对于这类项目数据库密码通常被写在application-druid.yml里而且因为框架本身已经集成了Druid再引入Jasypt有点重复。Druid自带的ConfigFilter本身就是干这个的而且它能在连接池层面直接解密不需要改业务代码。Druid的ConfigFilter支持两种加解密模式一种是私钥加密、公钥解密常用另一种是公钥加密、私钥解密。默认推荐前者私钥用来加密只能在本地安全保存公钥可以配置到项目的配置文件中因为它只用来解密即使泄露也无法反推密码。3.2 生成密钥对和数据库密码密文Druid提供了一个工具类ConfigTools用起来非常直接java -cp druid-1.2.20.jar com.alibaba.druid.filter.config.ConfigTools MyDocker2024执行后输出三行关键信息privateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCx2... publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAK2RHDw3JtBZvH3b... password: YR3V3rN7U3dBbSlQ2gLPOy0b7JbG5Xw我把password这一行即数据库密码的密文配置到数据源里把publicKey配置到filter属性里。3.3 配置Druid数据源解密在若依RuoYi这类框架中数据源配置通常长这样spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: filters: config,stat,wall,log4j connection-properties: config.decrypttrue;config.decrypt.key${DRUID_PUBLIC_KEY} password: YR3V3rN7U3dBbSlQ2gLPOy0b7JbG5Xw关键配置解释如下filters: config启用ConfigFilter注意要跟stat、wall等filter用逗号隔开。connection-properties这里的config.decrypttrue表示开启解密config.decrypt.key告诉Druid用哪把公钥解密。password填的是上一步生成的密文而不是明文。有同学可能会问公钥写成${DRUID_PUBLIC_KEY}环境变量和直接写明文有什么区别直接写在配置文件里确实也能跑通但这样公钥就和配置一起泄露了。因为私钥和密码是分开保存的泄露公钥本身不会直接导致密码泄露属于可接受但不够好。更好的做法是把公钥也放到环境变量或配置中心从源头减少暴露面。3.4 代码层面确认解密生效配置完成后如何确认它真的生效了我一般这样做SpringBootTest class DruidDecryptTest { Autowired private DataSource dataSource; Test void testConnection() throws Exception { Connection conn dataSource.getConnection(); System.out.println(连接成功 conn.getMetaData().getURL()); conn.close(); } }如果配置正确测试会正常通过如果公钥不对或者密文格式错误启动时就会看到Cannot resolve property或Exception decrypt之类的报错。这里我再提醒一句用Druid的ConfigTools生成密文后不要把生成的privateKey放在项目里。有些人为了方便把私钥贴在注释里这等于自己把门打开了。4. 若依框架的特殊场景与集成要点若依框架在社区里的使用量很大我在处理它的数据库密码加密时发现几个和普通Spring Boot项目不太一样的地方值得单独说一说。4.1 多数据源场景下的配置位置若依支持多数据源master、slave在application-druid.yml里是一个Map结构。加密时需要注意每个数据源都要单独配置connection-properties不能只配置一个就完事。也就是说master下的password要填master对应的密文slave下的password要填slave对应的密文公钥可以共用但密文不能混用。spring: datasource: druid: master: url: jdbc:mysql://localhost:3306/ruoyi password: ENC_RuoYiMasterPwd connection-properties: config.decrypttrue;config.decrypt.key${DRUID_PUBLIC_KEY} slave: url: jdbc:mysql://localhost:3306/ruoyi_slave password: ENC_RuoYiSlavePwd connection-properties: config.decrypttrue;config.decrypt.key${DRUID_PUBLIC_KEY}我见过很多人只加密了主库从库密码还是明文结果客户审计一查主库过了从库直接打回。这种部分加密比完全不加密还容易被质疑。4.2 监控页面导致的密码泄露隐患若依自带Druid监控页面/druid这个页面上能看到SQL执行详情包括一些参数。一旦数据库密码被误当成SQL参数拼接监控页面就会把密码明文展示出来。所以我对若依项目的建议是开启Druid监控页面一定要设置访问密码而且生产环境必须关闭。在application-druid.yml里这样配spring: datasource: druid: stat-view-servlet: enabled: true login-username: ${DRUID_MONITOR_USER} login-password: ${DRUID_MONITOR_PASSWORD} allow: 127.0.0.1这段配置和密码加密本身没有直接关系但它是密码安全的后置防线。没有这道防线前面做得再好一个暴露在公网的监控页面就能把底裤都揭了。4.3 集成进来之后的验证顺序我自己的习惯是改造完配置后按照下面这个顺序验证能省很多排查时间单元测试验证Druid解密只启动数据源相关上下文跑一个getConnection()。启动完整应用观察启动日志中有没有解密异常。验证登录用若依的管理员账号登录系统跑几个查询接口。检查监控页面确认没有把密码拼进SQL参数里。如果第1步就失败基本都是公钥/密文不匹配如果第2步失败大概率是filter没生效或jar包冲突如果第3步失败说明接口层对数据库连接的处理有特殊逻辑如果第4步有问题就要回到代码审查。5. 我踩过的坑完整排查链路记录理论安排完了讲点实际的。以下是我之前在一个Spring Boot项目中同时使用Jasypt和Druid时踩过的三个坑每个都花了几个小时才定位。我把排查过程完整记录下来希望帮大家少走弯路。5.1 启动报错Caused by: java.security.InvalidKeyException现象项目启动时Druid初始化数据源直接抛异常Caused by: java.security.InvalidKeyException: Illegal key size排查过程第一反应是JDK加密策略限制因为提示信息太明显了。我检查了三遍JDK版本和local_policy.jar都是正常的。后来仔细看异常栈发现异常不是由Druid解密抛出来的而是由Jasypt的StringEncryptor抛出来的——因为我在配置里同时设置了jasypt的ENC()占位符jasypt在解析spring.datasource.password时先执行解密结果AES-256算法被本地JDK拒绝。根因这个项目用的JDK是某厂商的定制版8u121确实没放开无限强度加密策略。Druid不受影响是因为它的ConfigFilter底层用的是RSA不涉及256位对称密钥而Jasypt默认走AES-256。解决方案# 方式1切换到JCE无限强度版本 下载jce_policy-8.zip替换到$JAVA_HOME/jre/lib/security/ # 方式2Jasypt降级算法 jasypt: encryptor: algorithm: PBEWithMD5AndDES我最后选了方式2因为那个项目是存量老项目不能随便升级JDK。这里提醒一句算法降级后在命令行生成密文时也要用同样的算法否则启动时密文解密不出来。5.2 Jasypt自定义加密器不生效现象我按官方文档自定义了StringEncryptor的Bean启动时也不报错但配置里的密文就是以原样注入到数据源里数据库连接失败密码变成了ENC(xxx)字符串本身。排查过程我先确认了Bean有没有被Spring扫描到——有。又确认了jasypt.encryptor.algorithm配置是否生效——也没有问题。后来我翻到jasypt-spring-boot的源码看到它启动时会通过StringEncryptor的Bean名称来查找用户自定义实现。根因我定义的Bean名称是myStringEncryptor不是jasypt默认查找的jasyptStringEncryptor。它找不到自定义Bean就回退到了默认加密器而默认加密器不知道我的算法和密钥所以对ENC()内容不做什么有效处理直接透传了。解决方案把Bean名称改为jasyptStringEncryptor或者使用Bean(jasyptStringEncryptor)显式指定。5.3 Druid连接池拿到解密后的密码但日志里还能看到明文现象加密配置好了功能正常但我在logback.xml里配置的SQL日志中能看到Druid打印的数据源password属性明文直接打在日志里。排查过程我检查了Druid的filter配置发现log4j这个filter被启用了。Druid的LogFilter会打印连接信息其中就包括DataSource的property列表密码字段会以明文形式输出。根因filters: config,stat,wall,log4j中的log4j过滤器在调试模式下记录连接参数把getPassword()打印出来了。解决方案spring: datasource: druid: filter: log4j: enabled: false connection-log-enabled: false statement-log-enabled: false同时在logback.xml中把Druid的com.alibaba.druid日志级别调成WARN避免日常调试时打印大量连接参数。这个问题不解决前面的加密等于白做——日志系统的泄露路径往往比服务器被攻破更常见。6. 密钥管理的进阶心得加密方案选好了、跑通了只是第一步。真正拉开差距的是密钥管理。我在多个项目里反复调整后沉淀出几条心得分享给大家。6.1 密钥必须和环境分离不管是Jasypt的jasypt.encryptor.password还是Druid的config.decrypt.key都不要写死在配置文件里。推荐的注入方式有三种环境变量最通用CI/CD平台都能配置。启动参数适合小规模手动部署java -jar app.jar --jasypt.encryptor.passwordxxx。配置中心微服务架构下最合适密钥单独放一个namespace并设置权限。密钥本身的强度也要注意。不要用123456这种口令当密钥Jasypt和Druid的密钥至少16位最好是随机生成的字符串。6.2 不要试图对所有配置都加密加密不是越多越好。我见过有人把server.port、spring.application.name都做成了ENC(...)结果每次改配置都要重新生成密文除了增加维护成本没有任何安全收益。有个简单的判断标准如果这个配置泄露了攻击者能不能直接利用能就加密不能就不加。数据库密码、第三方API的secret、支付商户号对应的私钥这些必须加密端口号、应用名、日志级别这些明文没关系。6.3 Jasypt、Druid之外的第三环密码加密只是单点防护。如果条件允许我还会建议再加两样东西一是把数据库账号权限最小化应用账号只授权对应库表的增删改查不给DDL权限二是开启数据库审计日志。这样的话即使密码被破解账号能做的事也被限制住了攻击面进一步收窄。密码加密解决的是密码别裸奔的问题账号权限解决的是万一裸奔了也别造成太大破坏的问题两层叠加才是完整的数据库安全方案。7. 最后给一份可直接对照的清单每次给项目做配置密码加密改造我都会照着下面这个清单过一遍确保没有遗漏[ ] 确认项目中所有敏感配置清单数据库、Redis、MQ、第三方API密钥等。[ ] 选择加密方案Jasypt全局加密或Druid ConfigFilter连接池加密必要时两者叠加。[ ] 生成密文时记录所用算法和密钥并在配置中保持一致。[ ] 密钥不要写入配置文件改用环境变量、启动参数或配置中心。[ ] 自定义加密器时Bean名称严格使用jasyptStringEncryptor。[ ] Druid多数据源时每个数据源单独设置connection-properties。[ ] 关闭或限制Druid监控页面的暴露范围并设置账号密码。[ ] 检查日志配置禁止打印数据源密码或SQL参数中的敏感值。[ ] 修改后验证顺序单测数据源连接、启动应用、功能冒烟、日志检查。[ ] 将密钥、生成密文的命令、配置文件模板沉淀到团队的文档里方便其他人接手。这份清单看起来琐碎但每一条背后都是我实打实踩过的坑。把密码加密做成团队习惯而不是个人技巧安全性才能真正持久。我在实际操作中的体会是一开始改造的时候觉得麻烦可一旦跑顺了后面新项目从第一行配置就开始注意这个问题反而省心很多。配置文件的明文密码就像家里大门不锁——你可能运气好一直没事但只要出事就是大事。趁现在成本低把密码加密这件事落地了吧。
返回列表