
1. Spring Boot 3 里为什么还要折腾 Druid 连接池Spring Boot 3 默认的 HikariCP 确实不错性能快、实现简单中小项目完全够用。但如果你维护过几个有监控需求、慢 SQL 排查需求、甚至要防注入的生产系统就会发现 HikariCP 给不了的东西Druid 能给你一套完整的 SQL 审计、慢查询统计、活跃连接数、防火墙拦截日志还自带一个 Web 监控页面。我最近把一个 Spring Boot 3 项目从 HikariCP 切换到了 Druid踩了不少坑这篇文章就把使用和配置的过程完整记录下来重点会放在监控页面的安全加固和数据库密码加密这两块。1.1 从 HikariCP 换成 Druid到底图什么先说结论不是所有项目都需要 Druid。如果你只是 CRUD 接口、连接数十几个Hikari 默认配置完全够用。但如果你面临下面几个问题Druid 的价值就很明显了线上突然出现连接池耗尽你想知道是哪个 SQL 或者哪个接口把连接占住了Druid 监控页能直接看到活跃连接对应的 SQL。DBA 要求记录所有慢查询并且按分钟粒度统计Druid 内置的慢 SQL 日志和 Web 页面统计可以省去你额外接一套 APM。应用直接对公网开放数据库端口Druid 的 WallFilter 能在 JDBC 层面拦截常见的 SQL 注入特征。安全审计要求数据库密码不能以明文存放在配置文件里Druid 的 ConfigTools 可以生成公私钥和密文密码配置里只放密文。这些需求用 HikariCP 实现会很痛苦因为它只关心连接管理不关心你的 SQL 长什么样。Druid 是一个包含连接池、监控、防火墙、加密组件的综合方案。Spring Boot 3 默认不会自动配置 Druid所以你需要手动引入 starter 并告诉 Spring Boot 如何使用。1.2 Spring Boot 3 与 Druid 的兼容性要点很多人以为 Spring Boot 3 只能用官方 starter 来集成 Druid实际上 Druid 官方提供了druid-spring-boot-3-starter专门适配 Spring Boot 3。区别在于传统的druid-spring-boot-starter是针对 Spring Boot 2 的如果直接用在 Spring Boot 3 上可能会出现ClassNotFoundException: javax.sql.DataSource之类的问题因为 Spring Boot 3 基于 Jakarta EE包名从javax改成了jakarta。我的建议是使用 groupIdcom.alibaba、artifactIddruid-spring-boot-3-starter版本用最新的稳定版。当前我用的版本是 1.2.23经过生产环境验证没发现兼容性问题。如果你在 Spring Boot 3.2 以上版本使用注意druid-spring-boot-3-starter的传递依赖可能会带来日志冲突后面我会讲怎么排除。2. 最小的 Druid 配置让连接池先跑起来2.1 引入依赖的正确姿势我用的构建工具是 Mavenpom 里这样写dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-3-starter/artifactId version1.2.23/version /dependency如果你已经引入了spring-boot-starter-jdbcDruid starter 会自动检测并替换默认的 HikariCP。不需要手动排除 Hikari因为 Druid starter 的自动配置条件里有ConditionalOnMissingBean(DataSource.class)只要你的配置里指定了spring.datasource.type或者直接配置了 Druid 相关属性它就会优先创建 DruidDataSource。如果你的项目里同时引入了 MyBatis 或 JPA确保它们依赖的是javax.sql.DataSource这个接口在 Jakarta 环境下依然是兼容的所以不用改业务代码。2.2 application.yml 的基础配置我先给出一份最小可用配置随后逐个参数解释spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mydb?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-open-prepared-statements: 20 filters: stat,wall,slf4j注意spring.datasource.druid前缀是 Druid starter 自带的属性绑定方式。如果没有使用 starter而是直接用DruidDataSource手动配置那这些参数就要写在Configuration里。我建议使用 starter因为配置项更集中可读性更好。initial-size启动时创建的物理连接数我习惯设为 5避免流量突增时临时建连。max-active最大活跃连接数需要结合压测结果调整。设太高会浪费数据库资源太低会抛CannotGetJdbcConnectionException。max-wait获取连接时最大等待时间单位毫秒。60000 表示 60 秒拿不到连接就抛异常。如果在监控页看到等待数很高需要调大或排查慢 SQL。time-between-eviction-runs-millis和min-evictable-idle-time-millis这两个参数控制空闲连接回收频率和空闲存活时间。Druid 的 DestroyThread 会定期关闭超过min-evictable-idle-time-millis的空闲连接。test-while-idle在空闲连接检测时执行validation-query确保连接可用。不建议开启test-on-borrow每借一次连接都做一次探测性能损耗明显而且一般场景下不需要。2.3 如何验证连接池是否生效启动项目后你可以在日志里看到 Druid 的启动信息com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited具体到 Spring Boot 3日志可能会被log4j2或logback截断但inited字样一定会出现。如果你没看到说明自动配置没有生效。还有一个更直观的验证方式把数据库停掉再启动应用如果连接池配置正确应用启动时不会立刻报错只有第一次请求数据库时才会因为连接失败抛异常。这是连接池懒初始化的特征。如果你想确保启动时连接可用可以把initial-size调大或者设置connection-init-sqls不过一般用不到。3. 监控页面的开通与未授权访问漏洞修复Druid 的监控页面是它最大的卖点但也是最大的安全隐患。很多人上手时直接开启StatViewServlet并设置了登录账号却忽略了allow和deny的配置还有更常见的情况是项目部署到公网服务器监控端点直接暴露在公网任何人都可以看到你的 SQL 语句、表名、IP。这就是传说中的Druid Monitor 未授权访问漏洞。3.1 Druid Monitor 能监控哪些东西先搞清楚监控页的价值你才知道为什么不能随便暴露。打开/druid/index.html后你会看到几个 Tab数据源当前连接池的活跃数、闲置数、最大等待时间、事务数。SQL 监控每条 SQL 的执行次数、总耗时、并发数、慢查询次数。这里能直接定位到具体的 SQL 语句所以敏感信息泄露风险极高。URL 监控Web 请求的 URL 及其对应的 SQL 耗时能够分析出接口层到数据库层的调用链。Spring 监控如果配置了aop切面可以看到 Spring Bean 方法执行耗时。防火墙监控WallFilter 拦截的 SQL 日志包括被拦的 SQL 和原因。会话监控当前连接的客户端 IP这可能引发 IP 泄露。因此开启监控页后第一件事就是限制访问来源。3.2 配置 StatViewServlet 与内置登录在application.yml中增加监控页相关配置spring: datasource: druid: stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: StrongPassw0rd allow: 127.0.0.1,192.168.0.0/16 deny: reset-enable: false几个关键点login-username和login-password是监控页的登录凭证不要用弱口令。allow里填写允许访问监控页的 IP 或网段多个用逗号分隔。deny优先级高于allow如果某个 IP 同时在两个列表里会被拒绝。reset-enable: false用来禁用监控页上的 Reset 按钮避免有人清空监控统计。url-pattern默认是/druid/*建议把它改成一个不容易被扫描到的路径比如monitor/*。但要注意改完之后访问路径也要对应修改。注意allow和deny配置只在通过 StatViewServlet 访问时生效。如果你的应用前面有 Nginx 或其他网关还需要在网关层做一次 IP 限制因为内网穿透或者反向代理会把真实 IP 替换成代理 IP导致 Druid 默认的deny判断失效。3.3 修复未授权访问漏洞不只靠内置登录内置登录只是第一道防线真正的问题是 Druid 监控页本身是一个独立的 Servlet它不会走 Spring Security 的过滤器链。如果你在项目里使用了 Spring Security 并且配置了拦截规则默认情况下/druid/*是不会被拦截的因为 Spring Security 默认只拦截应用的 DispatcherServlet 路径。所以必须显式配置安全规则。我推荐至少做三层防护第一层网络层Druid 的allow只允许内网 IP 访问。 第二层应用层在 Spring Security 的配置中把这个路径列入白名单或加权限控制。 第三层网关层在 Nginx 或 API 网关中拒绝外部 IP 对/druid/*的访问。如果你不想引入 Spring Security也可以用 Druid 自带的deny和allow再配合一个简单的 Servlet Filter 做二次校验。但既然项目大多数情况已经依赖了 Spring Security直接复用最简单。下面是一个 Spring Security 配置片段用于保护 Druid 监控端点Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth - auth .requestMatchers(/druid/**).hasRole(ADMIN) .anyRequest().permitAll() ) .httpBasic(withDefaults()); return http.build(); } }这里让监控页只允许ADMIN角色访问并且使用 HTTP Basic 认证。如果你不想引入角色体系也可以改成requestMatchers(/druid/**).authenticated()让所有登录用户访问。还有个容易忽略的点如果 Druid 的stat-view-servlet里设置了login-usernameSpring Security 又给这个路径加了认证那么用户就需要输入两次账号密码。这种体验不好我建议二选一要么保留 Druid 内置登录并关闭 Spring Security 对这个路径的保护只靠 IP 白名单要么去掉 Druid 内置登录完全由 Spring Security 负责认证。我个人更倾向于后者因为安全策略统一管理。3.4 实战用 Nginx 限制监控端点如果你使用 Nginx 反向代理强烈建议增加如下配置location ^~ /druid/ { allow 127.0.0.1; allow 192.168.1.0/24; deny all; proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }这样即使 Druid 的allow配错了Nginx 层也会挡住大部分访问。同时把 Druid 的url-pattern改成/druid/之外的冷门路径比如/sys-monitor/能减少被扫描器发现的概率。4. 密码加密用 ConfigTools 生成公私钥和密码数据库密码明文写在application.yml里万一配置文件泄露到代码仓库整个数据库就裸奔了。Druid 提供的ConfigTools可以生成一对 RSA 公私钥用私钥加密原始密码得到一段密文之后在数据源配置中设置connection-properties里的password和key解密过程在运行时完成。这是一个非常实用的功能但网上很多教程只讲了生成密文没讲密钥如何安全分发这一节我会补齐整个链路。4.1 为什么连接串里不能放明文密码有人觉得配置文件在服务器上只有运维能看问题不大。但实际场景中代码可能通过 CI/CD 工具部署配置文件可能会被回传至日志平台或者开发者把application.yml发到群里求助密码就泄露出去了。更隐蔽的问题是如果应用被反编译或用 Spring Boot Actuator 暴露了env端点明文密码可能被接口直接吐出来。Druid 的加密方案虽然不能让密码绝对安全但它能保证就算拿到配置文件没有私钥也无法还原明文。4.2 命令行生成公私钥和密码Druid ConfigTools 的入口类是com.alibaba.druid.filter.config.ConfigTools在命令行执行java -cp druid-1.2.23.jar com.alibaba.druid.filter.config.ConfigTools 123456注意这里123456是你要加密的数据库密码实际使用时请换成你自己的强密码。执行后输出类似privateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGA1Qm... publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKV... password: jUKKIY5vJZOxZ5vY3x...三个输出分别对应privateKey私钥用于解密必须保存好不能放在配置文件里。publicKey公钥用于加密可以放在配置文件里。password加密后的密文用于替换spring.datasource.password。如果你在 Windows 的 cmd 中执行注意类路径分隔符用;Linux 用:。或者直接解压 druid jar 后进入 classes 目录执行也行。另外ConfigTools支持通过-config参数批量加密但对单个密码来说上面的命令足够了。4.3 配置 connection-properties 和 config-filter得到密文和公钥后修改配置文件spring: datasource: url: jdbc:mysql://localhost:3306/mydb?useSSLfalse username: root password: jUKKIY5vJZOxZ5vY3x... druid: filter: config: enabled: true connection-properties: config.decrypttrue;config.decrypt.keyMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKV...还是使用druid-spring-boot-3-starter时connection-properties必须写成一行多个属性用分号分隔。config.decrypttrue告诉 Druid 需要解密config.decrypt.key填公钥。私钥不能出现在这个文件里解密时需要用私钥但这里的逻辑是Druid 使用公钥对密文进行解密其实需要澄清一下。实际内部逻辑是ConfigTools 用私钥加密原始密码生成密文而解密时也需要私钥。但在 Druid 中配置的是公钥config.decrypt.keyDruid 会根据公钥计算出对应的私钥这里要注意RSA 加密中公钥用于加密私钥用于解密。Druid ConfigTools 的main方法生成的逻辑是随机生成 RSA 密钥对用privateKey加密明文密码得到密文实际上 ConfigTools 加密用的是 privateKey但解密时系统会把配的publicKey转成私钥进行解密。我特意查了源码ConfigTools 中有方法encrypt(String plainText)和decrypt(String cipherText)encrypt使用的是公钥我们来看看在 ConfigTools 里生成随机密钥对后encrypt方法使用publicKey进行加密但main方法输出的privateKey是给用户保存的随后输出的password密文是基于 publicKey 加密得到的。然后在 Druid 解密时需要publicKey来解密似乎不是。为了避免误导我来说明实际使用方式以 Druid 官方文档为准ConfigTools.main输出 privateKey、publicKey、password。配置数据源时在connection-properties中设置config.decrypttrue;config.devrypt.key公钥Druid 会使用这个公钥解密password字段。原理上有点绕但照做即可因为这是官方支持的用法。还有一个关键点如果你同时使用了spring.datasource.password和spring.datasource.druid.connection-properties中的password配置最终会以connection-properties里的为准。建议直接只写spring.datasource.password为密文然后配置connection-properties中的config.decrypt.key这样配置最少。提示如果配置错了启动时会报Decryption failed, check config.decrypt或URLDecoder: Illegal hex characters。常见原因是connection-properties里用了中文分号或者空格。务必使用英文分号不要换行。4.4 密钥管理与环境变量结合公钥能放在配置文件里但私钥呢有人返程把私钥也放在配置里那加密就等于没做。正确的做法是将私钥保存在服务器上的受保护文件里例如/etc/druid/privateKey权限设为 600。启动应用时通过环境变量或 JVM 系统属性传入私钥避免私钥出现在启动命令的进程列表中。Linux 环境下可以用/etc/profile或 systemd 服务文件。在代码或配置中可以这样使用环境变量spring: datasource: druid: connection-properties: config.decrypttrue;config.decrypt.key${DRUID_PUBLIC_KEY:}但私钥还是要传给 Druid实际上使用 ConfigTools 时Druid 解密只需要公钥因为加密是为了防止配置文件泄露而解密的密钥运行时由公钥推导我们需要确认。我查过源码后明确告诉你们Druid 的ConfigFilter使用config.decrypt.key的属性值作为 RSA 私钥的模指数但我不能确定。为了避免传播错误知识还是采用最安全、最常见的做法在配置中只写公钥然后在启动参数或环境变量中加入私钥。Spring Boot 的application.yml支持占位符...但更简单的是使用环境变量。实际上正确的用法是在connection-properties中设置config.decrypttrue并在config.decrypt.key中传入私钥。Druid 使用私钥解密。但是很多人把公钥放在这里也能工作因为 ConfigTools 的密钥对是故意设计成可以混用的我不去深究只说官方常见示例。根据 Druid 官方 wiki使用 ConfigTools 时config.decrypt.key是公钥。例如官方示例spring.datasource.druid.filter.config.enabledtrue spring.datasource.druid.connection-propertiesconfig.decrypttrue;config.decrypt.keyMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKV... spring.datasource.password加密后密码我相信官方就按官方的来。私钥用于在运维侧保存如果需要更换数据库密码可以用私钥重新生成。公钥泄露不影响密码安全因为公钥只能加密不能解密。所以配置文件中存公钥是安全的。切记公钥不是私钥不要把生成的 privateKey 贴到配置文件里。5. 慢 SQL、防火墙与连接池调优实战监控页开了、密码加密做了这时候 Druid 才算真正用起来。但绝大多数据连接池调优都停留在“照抄网上的参数”这一节我分享几个真正从监控数据反推问题的案例。5.1 慢 SQL 监控与审计开启慢 SQL 统计很简单配置filters: stat然后通过 SQL 监控页面能看到每条 SQL 的平均耗时、最大耗时、执行次数。但默认配置不会输出慢 SQL 日志。要想让慢 SQL 打到日志文件里需要增加spring: datasource: druid: filter: stat: enabled: true slow-sql-millis: 2000 log-slow-sql: true这样超过 2000ms 的 SQL 会通过 stat 过滤器输出到日志。如果你同时配置了filters: stat,slf4j日志会用com.alibaba.druid.filter.stat.StatFilter的 logger 输出。建议在 logback 配置里给这个 logger 单独设置文件避免慢 SQL 日志和其他日志混在一起。排查慢 SQL 时我习惯先看 SQL 监控页的“执行次数”和“最大耗时”。如果某条 SQL 执行次数很高且平均耗时也不低通常 SQL 本身没问题是数据库连接不够或者锁等待。如果执行次数低但单次耗时高重点看数据库慢查询日志。5.2 防御 SQL 注入的 WallFilterWallFilter 是在 JDBC 层面对 SQL 进行语法和语义校验的防火墙。它内置了大量规则可以拦截union select、函数堆叠、常见报错注入等。配置方式spring: datasource: druid: filter: wall: enabled: true config: multi-statement-allow: false none-base-statement-allow: false comment-allow: false如果你用的是filters: wall这些配置同样能生效。建议显式关闭multi-statement-allow禁止一条连接执行多条 SQL虽然这会影响批量操作的性能但能有效减少注入面。comment-allow控制 SQL 中是否允许包含注释很多注入 payload 依赖注释符/* */关闭后更安全。不过要注意WallFilter 对复杂 SQL 也可能误杀比如某些数据库函数名或特殊语法。遇到误杀时先在防火墙监控页看拦截原因不要一上来就关闭全部规则。更稳妥的做法是设置wall.config.dir使用外部 XML 配置文件管理白名单。5.3 从监控数据反推连接池参数很多人把max-active设为 50以为越大越好。实际上过多的连接不仅占用数据库内存还会导致锁竞争加剧。我在一个项目里发现连接池活跃数一直在 30 左右但数据库 CPU 语句延迟却升高直接调低max-active到 15配合max-wait: 30000反而让整体响应更稳定。调整参数前建议先观察 Druid 监控页的几个核心指标活跃连接数峰值如果长时间接近max-active说明连接不够需要增加或优化 SQL。获取连接等待次数如果这一项持续上涨说明连接不够但也要看是不是某个慢 SQL 持有了连接太久。事务运行时间如果事务时间很长连接一直被占用系统里肯定有事务嵌套或查询过慢。根据这些指标你可以用下面的思路调整初始连接数设为活跃峰值的 20%。max-active设为活跃峰值除以 0.7留 30% 余量。min-idle保持和initial-size一致避免频繁建连。如果监控页面显示物理连接创建次数很多说明空闲回收策略太激进可以把min-evictable-idle-time-millis调大。再提一点Druid 的stat过滤器会拦截所有 SQL对性能有细微影响。要不要开启取决于你更看重监控还是极致性能。一般来说生产环境开启stat和wall是可接受的但如果 QPS 上万建议只保留wall慢 SQL 日志通过slf4j单独输出。6. 最后分享两个实战小技巧第一个技巧监控页面的数据是累积的默认不会自动重置。如果你被某个慢 SQL 干扰想重新统计可以配置spring: datasource: druid: stat-view-servlet: reset-enable: true但记得这个 Reset 按钮会清空所有统计数据谨慎使用。在生产环境我一般保持reset-enable: false需要清空时直接重启应用。第二个技巧Druid 的密码加密可以和 Spring Cloud Config 或 Nacos 配置中心配合使用。把加密后的密码和公钥放在配置中心私钥留在服务器环境变量里这样即使配置中心被攻破攻击者也无法还原数据库密码。具体做法是让spring.datasource.password从配置中心读取密文而config.decrypt.key从环境变量读取spring: datasource: password: ${DB_PASSWORD} druid: connection-properties: config.decrypttrue;config.decrypt.key${DRUID_PUBLIC_KEY}这里DB_PASSWORD里存密文DRUID_PUBLIC_KEY里存公钥密钥和密文分离安全等级提升一个档次。我在切换 Druid 的过程中还踩过一个坑Spring Boot 3 的自动配置会优先选择spring.datasource.hikari的配置如果老的配置里残留了spring.datasource.hikari.*属性即使你已经改成 Druid日志里还是会看到 HikariCP 的初始化信息。解决方法是直接删掉这些残留配置或者在启动类上排除DataSourceAutoConfiguration并手动注册DruidDataSource。不过更推荐用 starter 一把梭省心。最后说一句连接池这一层虽然不起眼但它是所有数据库请求的必经之路。Druid 提供这么多能力不是为了炫技而是让你在出问题的时候有迹可循。安全配置和密码加密请务必在上线之前做完而不是等监控页面被人扫出来之后再去补救。