首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >隐形的漏洞:一条被覆盖的Spring Security规则如何暴露Druid监控?

隐形的漏洞:一条被覆盖的Spring Security规则如何暴露Druid监控?

作者头像
烟雨平生
发布2025-07-09 14:43:32
发布2025-07-09 14:43:32
18500
代码可运行
举报
文章被收录于专栏:数字化之路数字化之路
运行总次数:0
代码可运行

又又又出问题了:

出安全漏洞了!!!!

图片
图片
图片
图片

稳定复现。

项目的身份认证是基于SpringSecurity的。难道是SpringSecurity出bug了?我不相信有人会特意花时间花精力去配置一个让druid页面未授权也可访问的规则,这不是多余嘛。现在哪个云数据库上没有慢SQL之类的监听,完全不需要使用druid自带的这个能力。

去项目确认下,的确没有关于“/druid/api.html”或“/druid/**”的规则。

难道是SpringSecurity出bug了?

查一查。

SpringSecurity比较庞大,从哪查起呢?断点打哪呢?

再补充点SpringSecurity的知识,方便理解:

安全框架有两个重要的概念,即认证(Authentication)和授权(Authorization)。 认证即确认主体可以访问当前系统的过程; 授权即确定主体通过认证后,检查在当前系统下所拥有的功能权限的过程。 这里的主体既可以是登录系统的用户,也可以是接入的设备或其它系统。 Spring Security会根据请求的URI的路径来确定该请求的过滤器链(Filter)以及最终的具体Servlet控制器(Controller)。 JackieTang,公众号:生活点亮技术Spring Security OAuth2是如何校验token的

图片
图片

从上图可以看到,Spring Security以一个单Filter(FilterChainProxy)存在于整个过滤链路中。这个FilterChainProxy代理着众多的Spring Security Filter。

再站在Tomcat(Web容器)的视角,来观察下一个请求从开始到结果都与哪些组件有交互,哪些先,哪些后。

图片
图片

容量评估实践:一个Tomcat最多能同时处理多少个HTTP请求?

通过上面的信息是不是可以有这样一个概念:

在基于SpringBoot的项目中,一个完整的网页请求,一定要与Filter组件进行交互!!!

SpringSecurity默认有12个Filter,

图片
图片

最终授权决策是在最后一个FilterSecurityInterceptor中。

授权相关组件

  • SecurityMetadataSource
    • 作用存储和管理安全元数据,根据请求的信息(如 URL)返回该请求所需要的授权配置属性(ConfigAttribute ),如hasRole('ADMIN') 、permitAll() 等。FilterSecurityInterceptor 会通过SecurityMetadataSource 获取请求对应的授权规则。
    • 示例DefaultFilterInvocationSecurityMetadataSource 是其常见实现类,它会将配置的路径和对应的授权规则进行映射存储,当请求到来时,根据请求路径匹配并返回相应的授权属性。
  • FilterSecurityInterceptor
    • 作用Spring Security 中进行授权决策的核心过滤器,它会获取请求对应的授权配置属性(通过SecurityMetadataSource ),然后结合用户的认证信息,调用AccessDecisionManager 进行授权决策,判断用户是否有权限访问请求的资源。
    • 示例当一个请求到达应用,且经过了前面的认证过滤器后,FilterSecurityInterceptor 会对其进行授权检查,决定是否允许该请求继续访问目标资源。

那就在FilterSecurityInterceptor类的这行代码中打断点

代码语言:javascript
代码运行次数:0
运行
复制
InterceptorStatusToken token = super.beforeInvocation(fi);

org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke

来看下Debug结果:

图片
图片

/druid/api.html命中了/**/*.html这条规则,获取了免认证授权。

图片
图片
代码语言:javascript
代码运行次数:0
运行
复制
logger.debug("Authorization successful");了

/**/*.html”这条规则是哪来的,没看到呢?

去项目找了下,果然还有一个。一个项目配置了两个

图片
图片
图片
图片

原因找到了:

是因为配置了“/**/*.html”这一系统授权规则,然后这个http://127.0.0.1:8080/druid/api.html不需要授权就可以正常访问。

解决办法就很简单了:这条规则删除掉。重启服务,这个页面就访问时就需要认证信息了。

图片
图片

另外,可以把Druid的这个特性关掉。

代码语言:javascript
代码运行次数:0
运行
复制
spring.datasource.druid.stat-view-servlet.enabled=false

现在的服务一般都是高可用的,至少有两个服务。但/druid/api.html页面是没有提供分布式Session管理的,如果是使用Token还能用,如果是使用Session完全用不了。为什么用不了?想一想,可以留言交流。

小结

遇到这类问题怎么办?先查规则配置文件,如果没有找到答案。

可以这样这样来排查:

1. 检查配置覆盖情况

  • 配置类顺序Spring Security 会按照配置类的加载顺序来应用配置。如果在多个配置类中都配置了授权规则,后加载的配置可能会覆盖先加载的配置。
    • 解决方法查看所有涉及 Spring Security 配置的类,确认是否存在多个地方对相同路径进行了授权配置。可以通过在配置类上添加 @Order 注解来调整配置类的加载顺序,@Order 的值越小,优先级越高。
  • 不同配置方式的优先级除了通过 HttpSecurity 进行配置外,还可能通过 AuthorizeConfigProvider 进行配置。不同方式配置的规则可能会相互覆盖。
    • 解决方法梳理代码中所有配置授权规则的地方,对比不同配置方式下对路径的配置情况。如果使用了 AuthorizeConfigProvider,检查每个 AuthorizeConfigProvider 的 configure 方法,确认规则的设置是否正确。

2. 排查配置加载顺序

  • 配置文件与代码配置的冲突如果同时使用了配置文件(如 application.properties 或 application.yml )和 Java 代码进行 Spring Security 配置,可能会出现配置冲突的情况。
    • 解决方法统一配置方式,尽量只使用 Java 代码进行配置,或者只使用配置文件进行配置。如果必须同时使用,仔细检查两者对相同路径的配置是否一致。
  • 自动配置与自定义配置的影响Spring Boot 的自动配置可能会引入一些默认的 Spring Security 配置,这些配置可能会与自定义配置产生冲突。
    • 解决方法查看项目的依赖,确认是否存在 Spring Security 相关的自动配置依赖。可以通过排除自动配置,然后手动进行完整配置来解决冲突。

3. 确认是否存在配置类冲突

  • 多个配置类对同一路径配置在多个配置类中,可能会不小心对同一个路径设置了不同的授权规则。
    • 解决方法在 IDE 中全局搜索涉及路径配置的代码,比如搜索 antMatchersrequestMatchers 等方法,查看所有对路径进行配置的地方,确认是否存在冲突。
  • 自定义过滤器或拦截器的影响如果自定义了过滤器或拦截器,并且在其中也进行了授权相关的配置,可能会影响 SecurityMetadataSource 的配置结果。
    • 解决方法检查自定义过滤器或拦截器的代码,确认是否存在对授权规则的设置。如果有,分析这些设置是否合理,是否与其他配置产生冲突。

4. 利用日志和调试工具排查

  • 启用 DEBUG 日志在项目的配置文件(application.properties 或 application.yml)中启用 Spring Security 的 DEBUG 日志,以便查看配置加载和规则应用的详细过程。 logging.level.org.springframework.security=DEBUG 通过查看日志,可以了解到配置是如何被加载和应用的,从而发现问题所在。
  • 使用调试断点在 FilterSecurityInterceptor 类的 beforeInvocation 方法中设置断点,查看 SecurityMetadataSource 是如何获取授权配置属性的。在断点处,可以查看 obtainSecurityMetadataSource().getAttributes(object) 方法返回的结果,以及 SecurityMetadataSource 内部的存储结构,进一步分析配置错误的原因。

通过以上步骤,基本可以定位到 SecurityMetadataSource 中授权规则配置异常的原因,并进行相应的修复

1、在排查原因的过程中发现用到@EnableWebSecurity,但SpringSecurity也可以正常工作,这是什么原因呢?

因为引入了组件spring-boot-starter-security 。

图片
图片
  1. Spring Boot 自动配置spring-boot-starter-security 引入 SecurityAutoConfiguration,通过 @Import 触发 WebSecurityEnablerConfiguration
  2. @EnableWebSecurity 的隐式加载WebSecurityEnablerConfiguration 通过 @EnableWebSecurity 导入 WebSecurityConfiguration
  3. Spring 上下文刷新在 refresh() 阶段,@Configuration 类被解析并创建 Bean。

Spring Boot自动配置通过SecurityAutoConfiguration隐式启用安全框架,无需显式注解,但显式添加可定制配置。

2、SpringSecurity各个组件是如何交互的

在 Spring Security 中,各组件通过精心设计的流程和交互机制协同工作,形成一个完整的安全防护体系。以下是它们的协作流程,以一个典型的 HTTP 请求为例进行说明:

一、请求进入过滤器链

  1. 请求到达 FilterChainProxy 所有请求首先进入 Spring Security 的核心过滤器 FilterChainProxy,它是安全过滤器链的入口点。
  2. 匹配 SecurityFilterChain FilterChainProxy 根据请求的 URL、HTTP 方法等信息,匹配到对应的 SecurityFilterChain。例如: http .authorizeRequests() .antMatchers("/api/**").authenticated()// 匹配 /api/ 开头的请求 .anyRequest().permitAll();// 其他请求允许访问

二、认证流程

  1. SecurityContextPersistenceFilter
    • 从 HTTP Session 或其他存储中恢复 SecurityContext(包含用户认证信息)。
    • 如果没有现有上下文,则创建一个空的 SecurityContext
  2. 认证过滤器处理请求 根据请求类型,不同的认证过滤器会尝试处理请求:
    • 表单登录UsernamePasswordAuthenticationFilter 拦截 /login 请求,提取用户名和密码,创建 UsernamePasswordAuthenticationToken
    • OAuth2 登录OAuth2LoginAuthenticationFilter 处理 OAuth2 回调,获取访问令牌并完成用户认证。
    • JWT 认证自定义过滤器解析请求头中的 JWT 令牌,创建 JwtAuthenticationToken
  3. AuthenticationManager 验证凭据 认证过滤器将 Authentication 对象(如 UsernamePasswordAuthenticationToken)传递给 AuthenticationManager。 Authentication authentication = authenticationManager.authenticate(token);
  4. AuthenticationProvider 执行验证 AuthenticationManager(通常是 ProviderManager)委托 AuthenticationProvider 验证凭据:
    • DaoAuthenticationProvider从 UserDetailsService 加载用户信息,比对密码。
    • OAuth2UserService处理 OAuth2 用户信息加载。
  5. 认证成功后的上下文存储 如果认证成功,AuthenticationManager 返回一个已认证的 Authentication 对象,过滤器将其存储到 SecurityContextHolder 中: SecurityContextHolder.getContext().setAuthentication(authentication);

三、授权流程

  1. FilterSecurityInterceptor 获取授权规则 当请求到达需要授权的资源时,FilterSecurityInterceptor 从 SecurityMetadataSource 获取该请求的授权配置(如 hasRole('ADMIN'))。
  2. AccessDecisionManager 执行投票 FilterSecurityInterceptor 将 Authentication、请求对象和授权配置传递给 AccessDecisionManager,后者组织 AccessDecisionVoter 进行投票: accessDecisionManager.decide(authentication, request, configAttributes);
  3. AccessDecisionVoter 投票 不同的投票者根据授权配置进行投票:
    • RoleVoter检查用户是否拥有指定角色。
    • WebExpressionVoter计算表达式(如 #oauth2.throwOnError(authenticated))。
  4. 决策结果处理
    • 允许访问请求继续通过过滤器链,到达目标资源。
    • 拒绝访问抛出 AccessDeniedException,由 ExceptionTranslationFilter 处理。

四、异常处理

  1. ExceptionTranslationFilter 捕获异常
    • 认证异常(如 AuthenticationException重定向到登录页(表单登录)或返回 401(REST API)。
    • 授权异常(如 AccessDeniedException返回 403 错误或重定向到拒绝访问页面。
  2. 清理资源 请求处理完成后,SecurityContextPersistenceFilter 将 SecurityContext 存储到 Session 中(如果启用了 Session),并清理线程上下文。

组件协作总结

阶段

核心组件

协作逻辑

请求分发

FilterChainProxy、SecurityFilterChain

根据请求匹配安全规则链

认证

AuthenticationFilter、AuthenticationManager、AuthenticationProvider

提取凭据 → 验证 → 存储认证信息

授权

FilterSecurityInterceptor、SecurityMetadataSource、AccessDecisionManager、AccessDecisionVoter

获取规则 → 投票决策 → 允许 / 拒绝访问

异常处理

ExceptionTranslationFilter

将安全异常转换为 HTTP 响应(如 401、403)

会话管理

SecurityContextPersistenceFilter

存储 / 恢复 SecurityContext

这种分层、模块化的设计使得 Spring Security 既强大又灵活,能够适应各种复杂的安全需求。

最后以一个大图结束:

图片
图片

但凡问题,皆有原因。

只是有些原因,你之前没有想到而已。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-07-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 的数字化之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 授权相关组件
  • 1. 检查配置覆盖情况
  • 2. 排查配置加载顺序
  • 3. 确认是否存在配置类冲突
  • 4. 利用日志和调试工具排查
  • 一、请求进入过滤器链
  • 二、认证流程
  • 三、授权流程
  • 四、异常处理
  • 组件协作总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档