又又又出问题了:
出安全漏洞了!!!!
稳定复现。
项目的身份认证是基于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中。
SecurityMetadataSourceConfigAttribute ),如hasRole('ADMIN') 、permitAll() 等。FilterSecurityInterceptor 会通过SecurityMetadataSource 获取请求对应的授权规则。DefaultFilterInvocationSecurityMetadataSource 是其常见实现类,它会将配置的路径和对应的授权规则进行映射存储,当请求到来时,根据请求路径匹配并返回相应的授权属性。FilterSecurityInterceptorSecurityMetadataSource ),然后结合用户的认证信息,调用AccessDecisionManager 进行授权决策,判断用户是否有权限访问请求的资源。FilterSecurityInterceptor 会对其进行授权检查,决定是否允许该请求继续访问目标资源。那就在FilterSecurityInterceptor类的这行代码中打断点
InterceptorStatusToken token = super.beforeInvocation(fi);org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke
来看下Debug结果:
/druid/api.html命中了/**/*.html这条规则,获取了免认证授权。
logger.debug("Authorization successful");了“/**/*.html”这条规则是哪来的,没看到呢?
去项目找了下,果然还有一个。一个项目配置了两个
原因找到了:
是因为配置了“/**/*.html”这一系统授权规则,然后这个http://127.0.0.1:8080/druid/api.html不需要授权就可以正常访问。
解决办法就很简单了:这条规则删除掉。重启服务,这个页面就访问时就需要认证信息了。
另外,可以把Druid的这个特性关掉。
spring.datasource.druid.stat-view-servlet.enabled=false现在的服务一般都是高可用的,至少有两个服务。但/druid/api.html页面是没有提供分布式Session管理的,如果是使用Token还能用,如果是使用Session完全用不了。为什么用不了?想一想,可以留言交流。
小结
遇到这类问题怎么办?先查规则配置文件,如果没有找到答案。
可以这样这样来排查:
@Order 注解来调整配置类的加载顺序,@Order 的值越小,优先级越高。HttpSecurity 进行配置外,还可能通过 AuthorizeConfigProvider 进行配置。不同方式配置的规则可能会相互覆盖。AuthorizeConfigProvider,检查每个 AuthorizeConfigProvider 的 configure 方法,确认规则的设置是否正确。application.properties 或 application.yml )和 Java 代码进行 Spring Security 配置,可能会出现配置冲突的情况。antMatchers、requestMatchers 等方法,查看所有对路径进行配置的地方,确认是否存在冲突。SecurityMetadataSource 的配置结果。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 。
spring-boot-starter-security 引入 SecurityAutoConfiguration,通过 @Import 触发 WebSecurityEnablerConfiguration。@EnableWebSecurity 的隐式加载WebSecurityEnablerConfiguration 通过 @EnableWebSecurity 导入 WebSecurityConfiguration。refresh() 阶段,@Configuration 类被解析并创建 Bean。Spring Boot自动配置通过SecurityAutoConfiguration隐式启用安全框架,无需显式注解,但显式添加可定制配置。
2、SpringSecurity各个组件是如何交互的
在 Spring Security 中,各组件通过精心设计的流程和交互机制协同工作,形成一个完整的安全防护体系。以下是它们的协作流程,以一个典型的 HTTP 请求为例进行说明:
FilterChainProxy
所有请求首先进入 Spring Security 的核心过滤器 FilterChainProxy,它是安全过滤器链的入口点。SecurityFilterChain
FilterChainProxy 根据请求的 URL、HTTP 方法等信息,匹配到对应的 SecurityFilterChain。例如:
http
.authorizeRequests()
.antMatchers("/api/**").authenticated()// 匹配 /api/ 开头的请求
.anyRequest().permitAll();// 其他请求允许访问
SecurityContextPersistenceFilterSecurityContext(包含用户认证信息)。SecurityContext。UsernamePasswordAuthenticationFilter 拦截 /login 请求,提取用户名和密码,创建 UsernamePasswordAuthenticationToken。OAuth2LoginAuthenticationFilter 处理 OAuth2 回调,获取访问令牌并完成用户认证。JwtAuthenticationToken。AuthenticationManager 验证凭据
认证过滤器将 Authentication 对象(如 UsernamePasswordAuthenticationToken)传递给 AuthenticationManager。
Authentication authentication = authenticationManager.authenticate(token);
AuthenticationProvider 执行验证
AuthenticationManager(通常是 ProviderManager)委托 AuthenticationProvider 验证凭据:DaoAuthenticationProvider从 UserDetailsService 加载用户信息,比对密码。OAuth2UserService处理 OAuth2 用户信息加载。AuthenticationManager 返回一个已认证的 Authentication 对象,过滤器将其存储到 SecurityContextHolder 中:
SecurityContextHolder.getContext().setAuthentication(authentication);FilterSecurityInterceptor 获取授权规则
当请求到达需要授权的资源时,FilterSecurityInterceptor 从 SecurityMetadataSource 获取该请求的授权配置(如 hasRole('ADMIN'))。AccessDecisionManager 执行投票
FilterSecurityInterceptor 将 Authentication、请求对象和授权配置传递给 AccessDecisionManager,后者组织 AccessDecisionVoter 进行投票:
accessDecisionManager.decide(authentication, request, configAttributes);
AccessDecisionVoter 投票
不同的投票者根据授权配置进行投票:RoleVoter检查用户是否拥有指定角色。WebExpressionVoter计算表达式(如 #oauth2.throwOnError(authenticated))。AccessDeniedException,由 ExceptionTranslationFilter 处理。ExceptionTranslationFilter 捕获异常AuthenticationException)重定向到登录页(表单登录)或返回 401(REST API)。AccessDeniedException)返回 403 错误或重定向到拒绝访问页面。SecurityContextPersistenceFilter 将 SecurityContext 存储到 Session 中(如果启用了 Session),并清理线程上下文。阶段 | 核心组件 | 协作逻辑 |
|---|---|---|
请求分发 | FilterChainProxy、SecurityFilterChain | 根据请求匹配安全规则链 |
认证 | AuthenticationFilter、AuthenticationManager、AuthenticationProvider | 提取凭据 → 验证 → 存储认证信息 |
授权 | FilterSecurityInterceptor、SecurityMetadataSource、AccessDecisionManager、AccessDecisionVoter | 获取规则 → 投票决策 → 允许 / 拒绝访问 |
异常处理 | ExceptionTranslationFilter | 将安全异常转换为 HTTP 响应(如 401、403) |
会话管理 | SecurityContextPersistenceFilter | 存储 / 恢复 SecurityContext |
这种分层、模块化的设计使得 Spring Security 既强大又灵活,能够适应各种复杂的安全需求。
最后以一个大图结束:
但凡问题,皆有原因。
只是有些原因,你之前没有想到而已。