资讯动态

Spring Boot数据库密码加密:Jasypt实战详解

发布时间:2026/10/21 15:03:20 来源:尧图企业网站定制
如果你的项目配置里还写着spring.datasource.password123456别急着往下滑这篇文章就是给你写的。把数据库密码明文放在 application.yml 里是很多 Spring Boot 项目常见的操作但同时也是安全审计里排得上号的高风险项。yml 文件一旦进了代码仓库意味着所有能看到代码的人、所有备份、所有 CI 日志、所有外包拷贝都可能顺走这个密码。Jasypt 3.0.5 配合 Spring Boot 做配置加密可以把敏感值变成 ENC(加密串)应用启动时自动解密代码仓库里看到的永远是一堆密文。这篇实战笔记会用 Spring Boot 2.7.x 作为基础环境完整走一遍依赖引入、密文生成、配置替换、启动验证、常见报错排查最后再聊几个生产环境才会用到的进阶细节。1. 为什么数据库密码不能明文写在 yml 里1.1 明文配置的泄露途径与真实风险我见过不少团队把数据库账号密码直接写在 yml 里理由无非是图省事、本地好跑、公司内网安全。但真实环境里泄露路径往往比你想象的更简单第一代码仓库的克隆权限通常覆盖开发、测试、运维甚至外包人员拿到代码等于拿到数据库凭据第二仓库的历史提交里如果出现过明文修改当前文件并不够老的 commit 一样能翻出来第三编译产物、日志文件、配置备份里也可能残留敏感信息第四如果仓库被同步到公共平台或发给外部协作方密码就彻底敞开了。这些路径互不相关但目标都是同一个数据库。更麻烦的是数据库密码不像接口密钥被泄露后可能很久都发现不了。等到数据库被脱库、被勒索才去翻配置历史往往已经太晚了。安全圈有个说法叫“凭据复用”大多数人习惯在测试环境、预发环境、生产环境用同一个密码哪怕你只泄露一个测试库攻击者也会顺手拿去尝试正式账号。所以这个问题的关键不在于“我们内网很安全”而在于一旦某个环节失守代价是整个数据资产。明文密码进仓库本质上就是把安全防线完全押在别人不犯错上这显然不现实。1.2 Jasypt 解决什么问题Jasypt 不是用来替代权限系统的它只解决一个核心问题让敏感配置在静态存储时不以明文出现。它的工作方式可以理解为一个“解密代理”——Spring 启动加载配置文件时Jasypt 会扫描所有属性值发现以ENC(开头、以)结尾的字符串就用指定密码和算法解出明文再去注入到对应的配置项中。所以你在 yml 里写的 password 是密文Spring 运行时拿到的才是真实值。这个机制的好处是加密逻辑对业务代码完全透明不需要改任何 Value 或数据源代码密文和密钥分离即使配置文件泄露只要密钥没有同时泄露密码就不会被还原。换句话说Jasypt 并不是让你变得绝对安全而是把“配置文件泄露 数据库密码泄露”这个等式给打破让攻击者拿到配置后还需要再攻一次密钥管理系统这中间的难度差距非常明显。2. 环境与依赖Spring Boot 2.7.x Jasypt Starter 3.0.52.1 版本选型该怎么定Jasypt 和 Spring Boot 的集成实际使用的是jasypt-spring-boot-starter这个第三方 starter它的版本号是独立的。我这次选 3.0.5是因为这个版本在 Spring Boot 2.x 上非常稳文档也最多遇到问题好查。如果你用的是 Spring Boot 3.x就要特别谨慎因为 Spring Boot 3 底层从 javax 迁移到了 jakarta老版本 starter 的自动配置可能无法直接工作。所以在动手之前先确认你自己的 Spring Boot 版本。我的建议是Spring Boot 2.2 到 2.7 优先使用 3.0.5 starterSpring Boot 1.x 可以选 2.1.2 之类的旧版本Spring Boot 3.x 则需要验证兼容性或者考虑自定义方案不要直接照抄本文的依赖。版本不是越新越好关键是和自己的项目大版本匹配。这里有一个容易混淆的点starter 版本号里的 3.0.5并不是指底层 Jasypt 核心库也一定是 3.x这个我放到文章后面专门解释。2.2 引入 Maven 依赖与基础配置在 pom.xml 中加一个依赖就够了starter 会把底层 Jasypt 库一起带进来不需要额外手动引入 jasypt 核心包。dependency groupIdcom.github.ulisesbocchio/groupId artifactIdjasypt-spring-boot-starter/artifactId version3.0.5/version /dependency接着在 application.yml 里加上 jasypt 加密器的配置。注意这里的jasypt.encryptor.password不要写死成常量必须用环境变量或外部化配置引用否则加密等于白做。我通常的做法是在配置里写${JASYPT_ENCRYPTOR_PASSWORD}在部署环境里 export 这个变量。jasypt: encryptor: password: ${JASYPT_ENCRYPTOR_PASSWORD} algorithm: PBEWITHHMACSHA512ANDAES_256 iv-generator-classname: org.jasypt.iv.RandomIvGenerator算法这里选用PBEWITHHMACSHA512ANDAES_256这是 Jasypt 新版本里推荐的强算法带随机 IV比老旧的 PBEWITHMD5ANDDES 安全得多。iv-generator-classname指定 RandomIvGenerator可以让每次加密同一个明文得到完全不同的密文避免通过密文比对推测明文。这两个参数和后面生成密文时的参数必须严格保持一致大小写也不能错否则启动时大概率解密失败。3. 实操生成密文并替换原有配置3.1 用 Jasypt 命令行工具生成密文加密方式有很多种可以在项目里写单元测试注入 StringEncryptor 来加密也可以在启动时通过接口生成但我最推荐的是用 Jasypt 自带的命令行工具干净利落不污染项目代码。下载 Jasypt 的 standalone 发布包解压后能找到对应 jar执行以下命令java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI \ inputMyDbPassword123 \ passwordMyJasyptSecret \ algorithmPBEWITHHMACSHA512ANDAES_256 \ ivGeneratorClassNameorg.jasypt.iv.RandomIvGenerator命令中的input是你要加密的数据库密码password就是 yml 里配置的那个 JASYPT_ENCRYPTOR_PASSWORD务必保持一致。算法必须和 Spring 端配置完全一致。执行后终端会输出一段以----OUTPUT开头的密文复制它备用。每次执行结果都不一样这是正常的因为使用了随机 IV只要密钥和算法一致Jasypt 在解密时都能正确还原。3.2 把密文写回 application.yml将原来的明文替换为ENC(密文)格式有严格约束前缀ENC(和后缀)必须与 starter 的默认规则一致中间不要加空格整个值最好用双引号包起来避免 yml 把括号解析成特殊符号。spring: datasource: url: jdbc:mysql://localhost:3306/yourdb username: root password: ENC(wlN0f...这里填生成的密文...)Jasypt 不仅支持加密密码数据库用户名、连接地址、Redis 密码、邮件账号这类敏感信息都可以用同一套方式处理。比如username也可以写成ENC(...)但需要注意解析时机所有被 Spring 管理的数据源属性、自定义配置、Value 注入都可以解密前提是配置项能被 Jasypt 的属性处理器扫描到。如果你的配置是通过自定义类加载的记得把加载器挂在 Spring 的 Environment 体系里否则不会自动解密。3.3 启动验证与解密结果确认配置完成后正常启动 Spring Boot。如果一切正常应用不会打印解密后的密码只会像平时一样连接数据库。为了确认解密结果一个简单的做法是在本地启动类里临时写一个 ApplicationRunner注入 Environment打印spring.datasource.password。但我的建议是只在本地验证不要在测试环境乱打日志避免密文被解密后再次暴露。启动时如果报错连接超时不要立刻怀疑 Jasypt先确认密文是否被正确解密再看网络和数据库状态。最直接的排查方式是看数据库连接池报错如果报的是 Access denied for user rootlocalhost (using password: YES)说明解密本身已经完成只是密码值不对如果报的是 Cannot load driver class 或 Failed to bind properties那更像是配置整体加载出了问题需要回去查 yml 和依赖。3.4 密钥必须和环境变量绑定密钥需要单独保存。常见做法是环境变量Linux 上export JASYPT_ENCRYPTOR_PASSWORDxxxWindows 上set JASYPT_ENCRYPTOR_PASSWORDxxx容器环境通过 secret 注入配置中心用配置项注入。切勿把密钥写到 yml 里哪怕是 test 环境。一个安全原则是密文可以在代码库密钥永远不能进代码库。密钥一旦泄露密文形同虚设所以密钥的保管是这套方案的核心。如果团队有专门的密码平台或密钥管理系统可以考虑把jasypt.encryptor.password指向外部服务获取后续我们会在进阶部分给出一个自定义加密器的思路。千万不要为了方便把密钥写在 IDEA 运行配置里还顺手提交到仓库这相当于把保险柜钥匙贴在保险柜门上比不加密还危险。4. 常见坑位与排查实录4.1 algorithm 不一致导致解密失败最常遇到的报错是Unable to decrypt data或Decryption failed。产生原因多半是加密时使用的算法、IV 生成器与 Spring 端配置不一致。比如你用 Jasypt 命令行工具加密时没有指定 algorithm命令行默认使用了旧算法而应用配置里用的是新算法结果必然解不开。另一个容易踩的是版本错位用旧版本库生成的密文到 3.0.5 starter 的环境里可能因为迭代算法不同而失败。所以我在生成密文时会把 algorithm 和 ivGeneratorClassName 作为固定参数写在命令里而不是依赖默认值。如果已经踩坑了最简单的解法是把应用配置里的算法和生成命令里的算法统一改成同一个强算法重新跑一次加密命令替换掉旧密文。不要指望同一个密文能在多套算法配置下通用这是不现实的。4.2 密文前缀解析失败如果启动时看到 Jasypt 无法识别ENC()前缀或者属性值被原样传递通常是因为属性解析器没有拦住。常见原因有两个一个是 yml 里密文格式不标准比如ENC小写、括号前后多空格、引号不匹配另一个是使用了非 Spring 管理的 PropertySource比如某些自定义的配置加载器绕过了 Jasypt 的拦截。建议先把密文整理成标准格式ENC(这里是一长串密文)确认 yml 没问题后再排查自己是不是有额外配置覆盖了属性源。另外如果你同时用了 Apollo 或 Nacos 这类配置中心要确认配置中心的属性流转回 Spring 的过程没有被截断否则 Jasypt 在本地文件里生效但远端配置不生效这种“部分解密”的现场最让人头疼。4.3 特殊字符与 YAML 转义问题数据库密码里如果包含特殊字符比如、#、%等在 yml 中容易触发解析问题。放进ENC()后由于密文通常是一段 Base64 字符串本身不含特殊符号相对安全但密钥password如果包含特殊字符在命令行或环境变量里需要处理好转义。命令行里建议把参数用双引号包起来环境变量里如果含空格或特殊符号注意 shell 的引号规则。为了避免这种烦恼我更推荐密钥使用一长串随机字符只包含字母和数字不包含任何需要在 shell 中转义的符号。生成随机密钥可以用openssl rand -base64 32然后手动去掉/这类符号或者直接用小写字母加数字的随机串。4.4 多数据源与多密文处理如果你的应用配置了主从数据源、多数据源或者用了 Druid、HikariCP 以外的连接池加密方式没有任何区别把每个需要保护的值分别加密在对应配置位置填入 ENC 密文即可。有一点要注意Jasypt 默认会对所有属性值做扫描包括自定义属性只要值为ENC(...)就会尝试解密。假如某些业务配置中确实需要存储以ENC(开头的字面量可以通过参数关闭扫描或者调整前缀规则但大部分场景用不到。多数据源时每个密码都要用同一个密钥加密这样维护成本最低。如果你不小心用不同密钥加密了两个数据源密码启动时一个能解密、一个报错那就只能重新生成出错的那个没有别的捷径。5. 进阶玩法自定义加密器与批量加密5.1 自定义 StringEncryptor Bean如果不想使用默认的自动配置或者需要从配置中心、密钥管理服务动态获取密钥可以自定义一个StringEncryptor。方法是在配置类里声明一个名为jasyptStringEncryptor的 Bean并在 yml 中设置jasypt.encryptor.bean指向它。Configuration public class JasyptConfig { Bean(jasyptStringEncryptor) public StringEncryptor stringEncryptor() { PooledPBEStringEncryptor encryptor new PooledPBEStringEncryptor(); SimpleStringPBEConfig config new SimpleStringPBEConfig(); config.setPassword(System.getenv(JASYPT_ENCRYPTOR_PASSWORD)); config.setAlgorithm(PBEWITHHMACSHA512ANDAES_256); config.setIvGeneratorClassName(org.jasypt.iv.RandomIvGenerator); config.setPoolSize(4); encryptor.setConfig(config); return encryptor; } }jasypt: encryptor: bean: jasyptStringEncryptor自定义 Bean 的意义在于可以把密钥来源从环境变量换成 API 调用每次启动时动态获取可以设置密钥池大小还可以用同一套代码支持多个算法。不过要记得在 Bean 里不要硬编码密钥最好还是通过环境变量或外部接口传入。自定义之后yml 里的默认算法配置可以不再设置代码里的SimpleStringPBEConfig会作为最终生效的配置。5.2 批量加密敏感字段的小脚本如果一个项目里有几十个配置项需要加密手敲命令行不现实。我一般写一个简短的 shell 脚本循环读取明文文件每条调用一次 Jasypt CLI把输出的密文追加到转换结果文件。脚本不会很长关键点是读取密钥使用环境变量而不是把密钥写在脚本里加密过程在本地或专门的安全机器上执行生成的批量密文再复制进 yml。#!/bin/bash while IFS read -r line; do if [ -n $line ]; then java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI \ input$line password$JASYPT_ENCRYPTOR_PASSWORD \ algorithmPBEWITHHMACSHA512ANDAES_256 \ ivGeneratorClassNameorg.jasypt.iv.RandomIvGenerator | grep OUTPUT fi done plain_credentials.txt脚本执行完记得清理临时明文文件不要把中间产物提交到仓库。这个脚本只建议在加密机器上执行不要放到生产环境的定时任务里否则等于用明文文件换一种方式暴露密钥和密码。批量生成以后人工抽查两条密文确保与源明文对应再整体替换到配置文件中。5.3 密钥轮换与运维策略很多团队问能不能直接改密钥让旧密文失效答案是不能Jasypt 解密的密钥与加密时绑定密钥变了旧密文就解不开。轮换的正确姿势是先在目标环境切换新的密钥同时重新生成所有敏感配置的密文然后一次性替换发布。为了避免运维时手忙脚乱我建议按模块分批替换并且保留一段时间的旧密钥用于回滚。这个思路类似证书轮换不要追求一键完成要保证可回退。还有一个细节如果你的配置是从配置中心拉取的确认配置中心的属性流转路径是否经过 Jasypt否则解密不会生效。密钥轮换时最好有一个独立的加密工具包只跑加密不接业务流量这样出问题影响面可控。6. 版本差异与 Spring Boot 3 兼容性提醒6.1 starter 版本和底层 Jasypt 库的关系这里要区分清楚两个版本jasypt-spring-boot-starter的版本比如 3.0.5和底层核心库 Jasypt 的版本。starter 只是把 Spring 的 PropertySource 和 Jasypt 的加解密能力桥接起来实际负责加密解密的还是 core 库。我们使用命令行加密时用的是独立发布的 Jasypt 工具包这个工具包的版本号可能和 starter 不同不要搞混。在 Maven 依赖里只需要引入 starter它会传递引入配套的核心库。如果你发现解密报错可以用mvn dependency:tree看一下 Jasypt 核心库的具体版本排除冲突。尤其是项目里如果早年自己引入过 jasypt 核心包版本和 starter 传递的不一致时很容易出现莫名其妙的算法兼容问题。遇到这种依赖冲突统一以 starter 传递的版本为准把手动引入的旧版本排除掉。6.2 Spring Boot 3 迁移时需要注意什么如果你已经在用 Spring Boot 3切莫直接引入 3.0.5 就完事。由于 Spring Boot 3 的包名从 javax 换成了 jakarta部分通过反射和代理实现的自动配置可能失效。实测中比较常见的现象是应用能启动但配置项没有被解密或者启动时就报 ClassNotFound。社区有相应的适配方案和大版本对应的 starter 版本不一定是 3.0.5需要去项目仓库的 release 信息确认。如果你的项目不能升级 starter还有一个兜底思路通过 Spring 的事件监听器在 Environment 准备完成后手动解密并重新设置属性或者把加密逻辑放到配置中心的解密接口里。这不是一条愉快的路但能救急。总体来说新项目直接上 Spring Boot 3 的话建议先做一个小 Demo 验证 Jasypt 的可用性再决定要不要全量铺开不要在核心业务链路上临时试错。最后分享一个我自己的使用习惯我会在项目的 README 中写清楚JASYPT_ENCRYPTOR_PASSWORD这个环境变量如何配置但绝不写上具体值在本地开发时我用 .env 文件配合启动脚本并把 .env 加入 gitignore在交付代码前会用 grep 扫一遍 yml 里有没有残留的明文 password。这套东西并不是什么高深技术但能在第一次被扫描工具盯上之前帮你少挨一次骂。如果你手头项目里还有明文密钥建议趁今天赶紧替换掉别等出了问题再后悔。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑