SpringBoot用jasypt加密敏感配置
配置文件里的数据库密码、API密钥等敏感信息不能明文存放。jasypt 插件支持自动加解密 ENC() 包裹的密文,也能自定义前缀和加密算法。关键是密钥别硬编码,要从环境变量或启动参数传入。
配置文件里躺着一堆敏感信息,数据库密码、Redis 密码、第三方接口的密钥,全是明文。
代码一提交,这些东西就跟着进了仓库,谁拉代码谁都能看见。
我在一个 SpringBoot 3.x 的项目上就被这个问题卡住过,试了好几种方案,最后落在 jasypt 这个插件上,用下来挺顺手。
这篇就把我踩过的点捋一遍:怎么接入、自动模式和自定义模式各自怎么玩,以及最容易翻车的密钥存放问题。
为什么要加密配置
先说清楚我们到底在防什么。
配置明文最大的风险不是"被黑客攻破",而是"无意泄露"。
代码仓库被 fork、日志里打印了配置、运维截图发群里,这些场景下明文密码就直接裸奔了。
所以目标很简单:让配置文件里看到的是一串密文,程序运行时再还原成真实值。
jasypt 干的就是这件事。
它在 Spring 读取配置的环节插了一脚,发现是密文就解密,发现是明文就原样放行。
对业务代码几乎无感,@Value 和 @ConfigurationProperties 该怎么写还怎么写。
引入依赖
用的是社区维护的 starter,自动配置能力都在这个包里。
<!-- jasypt 的 SpringBoot starter,自动接管配置解密 -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<!-- 版本按项目的 SpringBoot 版本挑,3.x 项目用 3.0.x 这一支 -->
<version>3.0.5</version>
</dependency>
加完依赖,@SpringBootApplication 启动时就会自动把解密能力装配进去。
整个属性源都会被它接管,系统属性、环境变量、application.yml 全在覆盖范围内。
自动配置模式
如果你没有自定义加密算法的需求,这个模式几乎零代码。
配一个加密密码
jasypt 需要一把"口令"来加解密,先在配置里给它。
# 这把口令是加解密的根,千万别直接写死在这里(下面会讲怎么安全存放)
jasypt.encryptor.password=MEBUGS
生成密文
拿着这把口令,用 jasypt 自带的命令行工具把明文加密。
去 Maven 本地仓库找到 jasypt 的 jar,执行:
# input 是要加密的明文,password 是上面那把口令,algorithm 指定算法
java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI \
input=mebugs_db_pwd \
password=MEBUGS \
algorithm=PBEWithMD5AndDES
跑完会吐出一串密文(每次结果都不一样,因为带了随机盐)。
这里插一句:PBEWithMD5AndDES 是老算法,强度一般。
新项目建议换成 PBEWITHHMACSHA512ANDAES_256 这种更现代的,jasypt 3.x 默认就是它,省得自己纠结。
密文填回配置
把生成的密文用 ENC() 包起来写进配置文件。
# ENC() 是 jasypt 的识别标记,括号里放密文
spring.datasource.password=ENC(G6N718UuyPE5bHyWKyuLQSm58eq...)
启动项目,jasypt 看到 ENC() 就自动解密,spring.datasource.password 拿到的是真实密码。
是不是没动一行业务代码?
自定义加解密模式
自动模式够用了,但有两种情况我会切到自定义:
- 想换掉
ENC()这个标记,用自己约定的前缀。 - 想用自己的加密算法,比如项目里已经有一套 RSA 工具类。
做法是实现 EncryptablePropertyResolver 接口,自己接管"怎么判断是密文"和"怎么解密"。
/**
* 自定义配置解密:用 EN*# 开头标记密文,内部走项目自己的 RSA 工具
*
* @author mebugs.com
*/
@Configuration
public class EncryptPropertyConfig {
// RSA 私钥。注意:这里只是演示位置,真实私钥绝不能硬编码(见最后一节)
private static final String PRIVATE_KEY = loadPrivateKeyFromEnv();
/**
* 覆盖掉 jasypt 默认的解析器,Bean 名字必须是这个
*/
@Bean("encryptablePropertyResolver")
public EncryptablePropertyResolver encryptablePropertyResolver() {
return new RsaPropertyResolver();
}
static class RsaPropertyResolver implements EncryptablePropertyResolver {
@Override
public String resolvePropertyValue(String value) {
// 不是我们约定的前缀,原样返回,当明文处理
if (value == null || !value.startsWith("EN*#")) {
return value;
}
// 砍掉前缀 EN*#,剩下的才是真正的密文
String cipher = value.substring(4);
try {
return RSAUtils.decrypt(cipher, PRIVATE_KEY);
} catch (Exception e) {
// 解密失败直接抛,让启动失败,别偷偷用一个错值跑起来
throw new IllegalStateException("配置解密失败: " + value, e);
}
}
}
private static String loadPrivateKeyFromEnv() {
// 从环境变量读私钥,而不是写死在代码里
String key = System.getenv("APP_RSA_PRIVATE_KEY");
if (key == null || key.isEmpty()) {
throw new IllegalStateException("缺少环境变量 APP_RSA_PRIVATE_KEY");
}
return key;
}
}
配置文件这边就改成自己的前缀:
# 用 EN*# 开头,jasypt 的 ENC() 标记在这个模式下就不认了
spring.datasource.password=EN*#MIGfMA0GCSqGSIb3DQ...
有个小坑我提一句:早期网上不少文章会把私钥直接写成 public static String PRIVATE_KEY = "..." 贴在类里。
这等于把保险箱和钥匙放一起,加密就白做了。
PS:官方文档对这点没特别强调,但真上线千万别这么干。
密钥到底该放哪
这才是整篇最该较真的地方。
加密的强度,最终取决于那把口令(或私钥)藏得够不够好。
如果口令还是明文写在 application.yml 里,那加密就是自欺欺人,攻击者拿到配置文件连带口令一起拿走。
几种我用过的存放方式,按推荐度排:
- 启动参数传入:
java -jar app.jar --jasypt.encryptor.password=真实口令,口令不落盘。 - 环境变量:
export JASYPT_ENCRYPTOR_PASSWORD=真实口令,配合容器编排的 secret 管理。 - 外部密钥服务:接 Vault、KMS 这类专门的密钥管理,程序启动时去拉,安全但接入成本高。
绝对不要做的:把口令提交进代码仓库,或者打进镜像的配置文件里。
一句话原则:密文可以进仓库,密钥不能进仓库。
小结
jasypt 解决的事情很聚焦,就是让配置文件里的敏感项以密文形态存在。
自动模式用 ENC() 包密文,零代码接入,日常够用。
自定义模式实现 EncryptablePropertyResolver,能换前缀、换算法,适合已有加密体系的项目。
但工具只是手段,真正的安全边界在密钥管理上:密文入库,密钥走启动参数或环境变量,别让钥匙跟保险箱躺在一起。
把这条守住,这套方案才算真的立住了。
