☰
基于凭据管理系统与Jasypt实现Nacos配置密码加密实战
2026/10/20 22:57:25 网站建设 项目流程

最近做安全基线整改,我把公司内部用 Nacos 做配置中心的应用全部过了一遍,发现一个被忽视很久的问题:数据库密码几乎是明文躺在 Nacos 的 YAML 配置里。跟几个同事聊,大家的反应都是“反正内网、还有账号鉴权”,可真等做完了“基于凭据管理系统实现 Nacos 服务端配置中数据库密码加密”的改造,再回头看,这个说法站不住脚。这篇文章把这次改造的来龙去脉、方案取舍、落地细节和踩过的坑完整写一遍,给你一个可参考的实践样本。

这套方案做下来,核心效果有两点:第一,Nacos 控制台、配置导出、客户端拉取的配置内容里,数据库密码不再是明文,而是类似ENC(prod-order-db-pwd-001)的引用串;第二,真实密码全部收归到凭据管理系统,由它统一做权限控制、解密审计和密钥轮换。也就是说,就算有人拿到了 Nacos 的配置,也只是拿到一个“凭据编号”,拿不到能连数据库的东西。文章偏实战,面向正在做配置中心安全治理、或者打算给 Spring Boot 应用加配置加密能力的同学。

1. 项目背景与改造动机:为什么不能再让明文密码躺在 Nacos 里

1.1 明文密码在配置中心的三类典型风险

第一类风险是配置数据被越权查看或导出。Nacos 的控制台虽然做了登录鉴权,但实际使用中,一个配置往往会被多个角色共享:后端开发要看、测试要连库、运维排查要验证配置、数据同学可能要核对连接信息。人一多,能访问配置中心的人就远远超出实际需要。只要其中有一个人账号被攻破,或者内部有人把配置复制出去,数据库的账号密码就等于直接暴露。

第二类风险是配置内容很容易被日志、监控和排查过程“带出来”。Spring Boot 启动时会把数据源相关信息打印到控制台,如果日志级别调到 DEBUG,JdbcUrl、username、部分密码信息很可能跟着出现。另外,数据库连接报错时,驱动会输出包含连接串的异常信息,万一连接串里就带着密码,日志系统一收集,密码又多了几个存放副本。明文密码的副本越多,泄露面就越大。

第三类风险是密码复用导致的横向扩散。我看过太多项目的数据库账号是“一处创建、多处复用”,同一个root或同一个业务账号被三四个服务共用,密码还一样。只要其中一个服务的配置泄露,攻击者拿到的就是多套系统的数据库入口。就算每套系统分开账号,如果 Nacos 配置里都明文写着同样的密码,结果也是一样的。

1.2 传统防护手段为什么不够用

有人会说,Nacos 不是有鉴权、有命名空间隔离吗?确实有,但鉴权只能解决“谁能不能进这扇门”的问题,解决不了“进来的人能不能看到密码”的问题。通常一个团队的成员都有权查看该团队服务的配置,这时候明文密码等于向团队内所有人默认开放。更麻烦的是,Nacos 默认的账号管理体系在多人共用账号时,连“到底谁看了配置”都查不出来。

还有人会想到把密码放到环境变量里,Nacos 里不写。这个思路部分有效,但引入的新问题也不少:环境变量在容器、虚拟机、本地开发环境之间容易不一致;密码散落在各个部署平台的配置里,没有统一变更入口;轮换一次密码需要同时改很多地方,漏一个就出事。最关键的是,环境变量里的敏感信息同样缺少审计和权限控制,本质上只是把明文搬了个位置。

所以这次改造的目标就很清楚了:让密码以密文或凭据引用的形式出现在 Nacos 配置中,真实密码集中存放在凭据管理系统里,应用启动时才向凭据系统申请解密。整个过程有权限校验、有调用审计、支持密码轮换,才算真正把风险收敛住。

2. 整体方案设计:凭据管理系统接管密码,Nacos 只存“暗号”

2.1 三种加密改造思路的对比

动手之前,我先把市场上常见的三种方案摆在一起做了个对比,避免一上来就钻代码。

方案实现思路优点缺点
A. 环境变量直接注入Nacos 只写${DB_PASSWORD},密码放到部署平台的环境变量里改造量小,不下发到配置中心密码分散在各环境,无统一审计,多机配置易漂移
B. Jasypt 加密 + 密钥放环境变量Nacos 存ENC(密文),应用启动时用环境变量里的密钥解密社区方案成熟,配置改造清晰密钥仍散落在环境变量;等于把明文换成密钥,密码泄露风险转化为密钥泄露风险
C. Jasypt + 凭据管理系统托管密钥/密码Nacos 存ENC(凭据ID),真正的密码或密钥保存在凭据管理系统,应用启动时调用凭据系统解密权限、审计、轮换统一收口,Nacos 中无任何明文和密钥需要额外依赖凭据管理系统,有集成成本

我最终选了方案 C。核心判断是:方案 B 虽然做到了“配置里没明文”,但密钥还是躺在环境变量里,一旦密钥泄露,数据照旧加密等于没加,而且密钥怎么轮换、谁看了密钥都难以追踪。方案 C 的价值在于把“密文存储”和“密钥管理”拆开了——密文放在 Nacos,密钥/真实密码放在凭据管理系统,中间加一层应用身份鉴权,安全性和可运维性都上一个台阶。

2.2 推荐架构与核心链路

改造后的核心组件有四个:Nacos 配置中心、Spring Boot 应用、凭据管理系统、目标数据库。整个启动链路大概是这样的:

  1. Spring Boot 应用启动,Nacos 客户端先从配置中心拉取对应dataId的配置;
  2. Spring 环境变量解析过程中,Jasypt 识别出形如ENC(prod-order-db-pwd-001)的值;
  3. Jasypt 调用自定义的StringEncryptor实现类;
  4. 自定义实现类向凭据管理系统发起解密请求,请求携带应用身份标识和凭据 ID;
  5. 凭据系统校验权限,把真实数据库密码返回给应用;
  6. 解密后的密码注入数据源配置,应用正常连库。

这里面 Jasypt 起的是“解密编排”作用,它本身不保管任何密钥,只是负责在 Spring 解析配置属性时,把ENC(...)标记的值转交给我们指定的解密器。真正的安全底座是凭据管理系统。用一个生活类比:Nacos 配置相当于办公室文件柜里的一张便签,上面写着“银行保险柜 3 号”,而凭据管理系统就是那家银行,取东西时要出示身份证、留下记录,而不是任何人都能从便签上直接看到保险柜密码。

顺手说一句,这套链路不只适用于数据库密码。Redis 密码、消息队列密码、第三方 API 密钥,凡是会被写进 Nacos 的敏感字符串,都可以用相同的ENC(凭据ID)方式接管。改造一次,后面要扩到别的敏感项就只是加配置、加凭据项的事。

2.3 安全模型设计:权限、校验和审计缺一不可

很多人以为把明文换成密文就完事了,其实安全模型设计才是关键。我这次在凭据管理系统侧做了三层约束,缺一层都可能被绕过。

最小权限原则。每个应用在凭据管理系统中注册独立的身份,只能申请自己命名空间下的凭据。比如订单服务只能解密prod-order-*开头的凭据,不能碰用户服务的数据库密码。这个看起来理所当然,但在实际操作中发现相当多系统是“一个密钥到处用”,如果不先做账号和命名空间规划,后面的权限控制根本无从谈起。

双重身份校验。应用调用凭据系统解密接口时,不能只凭一个凭据 ID 就放行。我要求同时校验应用身份(AppId + 签名)、来源 IP 网段、请求时间戳,签名用非对称方式生成,防止请求被伪造或重放。这样即使攻击者拿到 Nacos 里的ENC(prod-order-db-pwd-001),他也无法通过凭据系统的校验,等于拿着一张没带身份证的便签去银行取保险柜。

调用审计留痕。凭据系统的每次解密请求都记录操作者身份、请求时间、目标凭据、应用来源。出问题的时候,能从审计日志反查出“谁在什么时候解密过什么”。这一步不能省,后面安全团队要求提供数据访问溯源时,它就是唯一的依据。

3. 落地实现完整步骤:Spring Boot 结合凭据管理系统的前后端改造

3.1 Nacos 配置项改造:把明文密码替换为凭据引用

先说改造前 Nacos 里数据库配置长什么样,通常是这样一段 YAML:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://10.20.3.9:3306/order_db?useSSL=false&characterEncoding=utf8 username: order_rw password: 'OrderDb@2024!'

这段配置里,password就是数据库的真实密码,任何一个能查看该配置的人都能直接连库。改造后,我把它改成下面这样:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://10.20.3.9:3306/order_db?useSSL=false&characterEncoding=utf8 username: order_rw password: ENC(prod-order-db-pwd-001)

关键变化是password字段从明文变成ENC(prod-order-db-pwd-001)。这里ENC()是 Jasypt 默认的加密前缀和后缀,括号里放的是一个凭据 ID,不是密码本身。凭据 ID 的命名我建议按环境-应用-用途-序号来组织,比如prod-order-db-pwd-001,一目了然。注意凭据 ID 本身只包含小写字母、数字和短横线,不要加入空格或特殊字符,否则 YAML 解析和后续正则匹配都会踩坑。

有人会问,用户名username要不要也加密?我的做法是优先加密密码,用户名可以后续再纳入凭据管理。因为用户名相对不算高敏感,但也不是不能加密,同理会变成ENC(prod-order-db-user-001)。看你团队的安全要求,如果要求高,把用户名也放进凭据系统,改动上没有任何额外成本。

3.2 引入 Jasypt 并自定义 StringEncryptor 对接凭据管理系统

配置项改造完成后,下一步是让应用能够识别ENC(...)并完成解密。这里我用的还是 Jasypt 这套成熟方案,但它不再持有什么机密,只是一个“解密分发器”。

首先是依赖引入,在 Spring Boot 应用的pom.xml里增加:

<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> </dependency>

版本号我一般不写死,由项目 parent 统一管理,避免和 Spring Boot 版本冲突。如果你的项目用的是 Spring Boot 3.x,需要确认一下对应 starter 的版本兼容性,遇到问题优先升级到 3.x 兼容版本。

接下来在启动类上加上注解,让 Jasypt 接管所有配置属性的解密:

@EnableEncryptableProperties @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

然后是核心部分:自定义一个StringEncryptor实现类,把它声明为 Spring 容器中的一个 Bean,并告诉 Jasypt 使用这个 Bean。示例代码如下:

@Component("credentialEncryptor") public class CredentialStringEncryptor implements StringEncryptor { private static final Pattern ENC_PATTERN = Pattern.compile("^ENC\\((.+?)\\)$"); @Resource private CredentialService credentialService; @Override public String encrypt(String plaintext) { // 加密操作一般在管理端或发版工具中执行,运行时很少调用 String credentialId = credentialService.registerSecret(plaintext); return "ENC(" + credentialId + ")"; } @Override public String decrypt(String encryptedValue) { Matcher matcher = ENC_PATTERN.matcher(encryptedValue); if (!matcher.matches()) { throw new IllegalArgumentException("unsupported encrypted format: " + encryptedValue); } String credentialId = matcher.group(1); return credentialService.fetchSecret(credentialId); } }

这段代码里,encrypt方法用于把明文密码注册到凭据管理系统并返回凭据 ID。但在生产环境,我一般不会让应用在运行时调用encrypt,而是由管理端工具或 CI/CD 流程来做,避免把“写入密码”的能力暴露在业务进程里。decrypt方法则是真正的运行时路径:解析出ENC()括号里的凭据 ID,然后调用凭据管理系统的 SDK 或 HTTP 接口拿到真实密码。

最后在application.yml里指定 Jasypt 使用这个自定义解密器:

jasypt: encryptor: bean: credentialEncryptor property: prefix: "ENC(" suffix: ")"

这里prefix和suffix其实可以省略,因为ENC()就是 Jasypt 的默认配置,写出来是为了让团队同事在阅读代码时知道这套自定义标记是哪些。bean字段是关键,它告诉 Jasypt 去容器里找名为credentialEncryptor的StringEncryptor实现。

3.3 本地开发环境的兜底策略与强制校验开关

改造过程中第一个让我头疼的问题,就是本地开发环境没有凭据管理系统可用。总不能让每个开发在自己电脑上再搭一套凭据系统,那成本太高了。我的处理方式是在配置文件里增加一个开关,区分“强制凭据解密”和“本地兜底”两种模式。

生产、预发环境的配置强制走凭据解密:

credential: decryption-enabled: true

本地开发环境的配置则允许兜底,直接解密失败时回退到开发库密码:

spring: config: activate: on-profile: dev credential: decryption-enabled: false

在CredentialStringEncryptor.decrypt方法里加一段判断:如果decryption-enabled为 false,直接返回本地配置的真实密码;如果为 true,则必须从凭据系统获取,获取失败就抛出异常、终止启动,不允许带病运行。这个“fail fast”设计很重要,避免生产环境凭据系统故障时应用带着错误的连接偷偷跑起来。

再补充一个安全细节点:生产环境的兜底开关必须彻底关闭,不能留任何“本地密码兼容”。我见过有团队为了省事,在生产配置里也保留了一段可用的明文密码,结果 Nacos 一拉配置就等于明文还是公开了,改造等于没做。建议在发布前用 CI 脚本扫描配置文件,直接禁止生产环境配置中出现decryption-enabled: false或者常见密码字段。

3.4 配置热更新后的密码刷新处理

Nacos 支持配置动态刷新,这是很多人引以为傲的功能。但在数据库密码加密这个场景下,动态刷新并没有想象中那么简单。问题在于:即使 Nacos 里password字段从ENC(credA)改成ENC(credB),Jasypt 也会在新配置加载时再次解密,但数据源连接池里的连接还是基于旧密码建立的,HikariCP 不会因为你改了配置属性就自动重建连接池。

调研后我给了团队两套方案,按变更频率选:

低频变更,改密后重启应用。比如安全团队要求季度轮换,直接滚动重启一批实例就行,几十秒到几分钟的停机窗口在大部分内部系统可接受。

高频轮换,上连接池重建机制。在应用里监听 Nacos 配置刷新事件,检测到数据源相关配置变化时,主动关闭现有连接池再重新创建。主要逻辑是在刷新监听器里调用数据源的close(),然后重新构建DataSourceBean。注意这一步要处理并发请求,关闭和重建之间会有短暂的连接中断,尽量选业务低峰期执行。

我个人更推荐先用“低频重启”跑通全流程,等团队对这套机制有信心了,再去做无重启轮换。一上来就搞连接池热更新,容易被各种边界问题缠住。

4. 踩坑实录:启动失败、热更新失效与权限问题的排查技巧

4.1 我实际遇到过的几个典型问题

第一个典型问题是服务启动直接报解密失败。第一次改造测试时,某个应用启动日志里出现Decryption failed,一开始怀疑是 Jasypt 配置问题,查了一圈发现是凭据管理系统的网络策略没放行,应用所在网段访问不了凭据系统 API。这个最容易踩,因为本地验证时网络是通的,一上测试环境就被防火墙拦了。排查顺序建议是:先确认网络连通性,再确认应用身份权限,最后看凭据 ID 是否真的存在于目标命名空间。

第二个问题是 Nacos 控制台刷新配置后,应用日志显示密码确实变了,但数据库连接仍然失败。这就是上面说的连接池重建问题。当时我查看连接池状态,发现 HikariCP 里还是旧连接,配置属性变了不等于底层连接会销毁重建。后来加了配置刷新监听器,在检测到spring.datasource.password变化时主动关闭连接池,才算解决。

第三个问题比较隐蔽:密文被日志打印出来了。有一次排查问题时,同事把配置对象的toString()打到了日志里,结果包含ENC(prod-order-db-pwd-001)的整段配置进了日志系统。虽然这不算泄露真实密码,但凭据 ID 是绕过权限校验的第一步线索,不能放任不管。后来我在日志过滤器里加了脱敏规则,凡是匹配ENC(...)的内容统一替换成ENC(***)。

还有一个版本兼容问题。Spring Boot 2.x 时代用jasypt-spring-boot-starter很顺,但到了 Spring Boot 3.x,Jasypt 内部的字节码处理方式变了,旧版本会出现反射相关异常。遇到这类问题不要死磕,直接升级 starter 到与 Spring Boot 3 兼容的版本,基本都能解决。

4.2 问题速查表与调试建议

我把这次改造过程中高频遇到的问题整理成一张速查表,方便后续团队排查:

现象常见原因排查命令/手段解决思路
启动报 Decryption failed网络不通、权限不足、凭据 ID 不存在查看异常码和日志中的 credentialId按 网络→权限→凭据ID 顺序确认
控制台看到明文密码实例还在旧版本,未走解密链路检查实例代码版本和 Jasypt 日志分批重启实例,确保全部走新配置
配置热更新后连不上新库连接池没有重建查看连接池 active 连接情况加 Nacos 刷新监听器,主动重建 DataSource
日志打印出 ENC(...)配置对象被直接序列化输出搜索日志中的 ENC 关键字增加日志脱敏过滤规则
Spring Boot 3 启动异常starter 版本不兼容查看反射相关异常栈升级 jasypt starter 版本

调试手段方面,推荐三个快速验证方法。第一,用 curl 直接拉一次 Nacos 配置,确认远端配置里已经是ENC(...)而不是明文;curl 的接口路径以你们 Nacos 版本为准,重点是拉回来的response body里看password字段。第二,把应用日志级别调整到 DEBUG,观察 Jasypt 是否真正接管了ENC()标记的配置;如果日志里出现Decrypting property相关记录,基本可以断定解密链路已生效。第三,直接调用凭据管理系统的查询接口,用 Postman 或 curl 输入一个测试凭据 ID,确认凭据系统本身没问题,这样能把“应用问题”和“凭据系统问题”快速分开。

5. 扩展实践:密钥轮换、审计告警与落地推广的几点经验

5.1 数据库密码轮换如何做到“无感”

密码轮换是所有安全改造里最考验设计的一环。传统做法是改数据库密码,再改 Nacos 配置,再重启服务,麻烦且容易在某一台机器上漏改。用凭据管理系统之后,我设计了一套更顺滑的流程。

在凭据管理系统中给同一份密码增加版本,比如prod-order-db-pwd-001下同时存在v1和v2两个值。先让应用继续使用v1,然后把数据库侧密码改为v2,接着更新凭据系统里prod-order-db-pwd-001的生效版本。应用在下一次获取凭据时自然拿到v2。因为 Nacos 配置里写死的还是ENC(prod-order-db-pwd-001),这个凭据 ID 不变,所以不用改 Nacos,也不用重启;只要连接池能按上一节说的方式重建,轮换过程对用户基本无感。

如果凭据系统不支持版本化,也可以走“双写双读”的方式:在 Nacos 配置中临时增加一个spring.datasource.secondary-password,应用同时支持新旧两套密码连接数据库,等所有连接都切到新密码后,再移除旧密码。这个方案没有版本化那么优雅,但兼容旧凭据系统。

5.2 审计与告警:解密行为要可追溯、可预警

密码加密改造做完之后,我把审计和告警当作“验收标准”。光把明文藏起来不叫安全,得让人知道谁在解密、什么时候解密、解了多少次才叫闭环。

我这里落地了三类告警规则:解密频率突增告警,正常情况下一个应用启动时解密几次,运行期几乎不调用;如果某个应用在短时间内频繁调用解密接口,大概率是代码 bug 或账户被滥用。解密失败次数超阈值告警,连续失败超过 5 次就触发告警,说明凭据系统或应用身份可能出现异常。非工作时间大规模解密告警,这个可以根据团队实际情况调整,主要是防数据被批量拖走。

审计日志的留存周期建议至少一年。配合 Nacos 控制台的访问日志,安全团队可以完整还原一条链路:谁看到了配置、谁触发了解密、解密出来的密码在哪个时刻被哪个 IP 使用。这在内部审计和外部合规检查时非常有说服力。

5.3 最后一公里的落地推广建议

这次改造踩完坑,我个人最大的体会是:技术选型反而简单,真正难的是让团队愿意用、持续用。有几个经验值得分享。

第一,先用一个系统跑通全流程再铺开。不要一开始就追求把所有应用全部改造完。选一个配置简单、影响面小的服务作为试点,把从 Nacos 改造、凭据系统注册、发布验证到轮换演练的全流程走一遍,把文档沉淀下来。后面推广就是复制粘贴的事。

第二,尽量把加密能力封装成公共 Starter。如果团队里几十个 Spring Boot 应用,每个服务都自己实现一套StringEncryptor,维护成本很高,也容易出现配置不一致的问题。我后来把CredentialStringEncryptor、凭据系统客户端、日志脱敏规则全部打成了一个公共组件,业务方只需要引入依赖、配置两三个字段就能接入。

第三,不要在项目初期就把权限、审计、告警全部铺开,那样步子太大容易顾此失彼。先把“Nacos 中无明文”这个核心目标落地,再逐步完善权限模型和告警体系。安全建设不是一次性的,它更像是补丁迭代,先解决最大风险,再处理次要风险。

最后再提醒一件事:数据源密码改造只是配置安全的一部分,回头看看你的 Nacos 里还有没有别的明文敏感项,比如密钥、Token、回调地址的鉴权信息。加密这条路一旦走通,把其他敏感项纳入进来就只是“加一个凭据、改一行配置”的事了。希望这份实践方案能帮你少走几步弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询