news 2026/9/13 7:05:12

Spring Security 请求全过程解析:从 DelegatingFilterProxy 到 FilterSecurityInterceptor 的过滤器链源码追踪

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Security 请求全过程解析:从 DelegatingFilterProxy 到 FilterSecurityInterceptor 的过滤器链源码追踪

Spring Security 请求全过程解析:从 DelegatingFilterProxy 到 FilterSecurityInterceptor 的过滤器链源码追踪

【免费下载链接】source-code-hunter😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter

Spring Security 是基于 Spring 的安全框架,核心能力是认证(Authentication)与授权(Authorization)两大模块。与 Apache Shiro 相比,它功能更强大,可轻松自定义扩展,且对常见 Web 安全攻击提供内置防护。本文以 Spring Boot 集成 Spring Security 为切入点,从源码层面完整追踪一次 HTTP 请求在安全过滤器链中的流转全过程——从DelegatingFilterProxy接收请求,到FilterChainProxy调度各过滤器,再到FilterSecurityInterceptor完成授权决策并抛出异常、最终重定向到登录页的完整链路。读完本文,你将掌握 Spring Security 过滤器链的核心成员职责、请求在链上的判定逻辑、匿名认证与访问决策机制,以及如何用 Debug 断点亲手验证这一过程。

本仓库对应的完整源码笔记为 SpringSecurity 请求全过程解析,并可与 SpringSecurity 自定义用户认证、SpringSecurity 流程补充 配合阅读。

环境准备:Spring Boot 集成 Spring Security

本文使用 Spring Boot 版本2.5.3,Spring Security 版本5.5.1。使用 IDEA 创建一个 Spring Boot 项目后,在构建脚本中引入spring-boot-starter-security

dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.projectlombok:lombok:1.18.8' annotationProcessor 'org.projectlombok:lombok:1.18.8' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' }

接下来创建一个HelloController,对外提供一个/hello服务:

@RestController public class HelloController { @GetMapping("hello") public String hello() { return "hello world"; } }

直接启动项目并访问http://localhost:8080/hello,会发现页面被跳转到了一个登录页面:

默认的用户名为user,密码由 Spring Security 自动生成。回到 IDEA 的控制台即可找到密码信息:

Using generated security password: 4f06ba04-37e9-4bdd-a085-3305260da0d6

输入用户名user、密码4f06ba04-37e9-4bdd-a085-3305260da0d6后,即可成功访问/hello接口。

版本差异提示:在基于 spring-boot-dependencies 2.7.7 的新版本中,控制台日志会略有不同,输出为Using generated springSecurity password: ...并附带 "This generated password is for development use only..." 的提示。该默认用户由UserDetailsServiceAutoConfiguration自动装配的InMemoryUserDetailsManager提供,属于非持久化的内存映射实现,主要用于测试和演示,生产环境必须替换为自定义的用户信息来源(详见 SpringSecurity 流程补充 与 SpringSecurity 自定义用户认证)。

基本原理:启动加载链路与请求执行链路

Spring Security 默认为我们开启了一套安全配置。要理解它,需要先弄清两条链路:启动时的加载链路请求到达时的执行链路

当 Spring Boot 项目配置了 Spring Security 后,其整个加载过程如下图所示:

而当我们访问http://localhost:8080/hello时,代码的整个执行过程如下图所示:

如上图所示,Spring Security 包含了众多的过滤器,这些过滤器形成了一条链(Filter Chain),所有请求都必须通过这些过滤器后才能成功访问到资源。一次典型的未认证访问会经历:

  1. GET /hello:请求进入过滤器链,UsernamePasswordAuthenticationFilter判定不是登录提交(非 POST 的/login),DefaultLoginPageGeneratingFilter判定不是登录页请求,AnonymousAuthenticationFilter生成匿名认证 Token,最终FilterSecurityInterceptor判定匿名用户无权限,抛出AccessDeniedException,请求被重定向到/login
  2. GET /loginDefaultLoginPageGeneratingFilter判定是登录页请求,直接返回登录页面;
  3. POST /loginUsernamePasswordAuthenticationFilter判定是登录提交,封装并校验用户名密码,认证通过后重定向回原请求路径/hello
  4. GET /hello(认证后):过滤器链一路放行,请求最终到达HelloController,返回 "hello world"。

过滤器链的核心成员

请求执行链路中比较重要的几个过滤器(均位于FilterChainProxy的过滤器链内)有:

过滤器核心职责关键判定方法
UsernamePasswordAuthenticationFilter拦截登录提交,封装用户名密码并认证requiresAuthentication():是否匹配登录提交(POST 请求)
DefaultLoginPageGeneratingFilter生成默认登录页面isLoginUrlRequest():请求路径是否为/login
AnonymousAuthenticationFilter为未认证请求创建匿名身份检查SecurityContextHolder中是否存在认证信息
ExceptionTranslationFilter捕获过滤器链中抛出的安全异常handleSpringSecurityException():区分认证异常与访问拒绝异常
FilterSecurityInterceptor对受保护资源做最终授权决策委托AccessDecisionManager投票决定放行或拒绝

Debug 验证请求全过程:一行一行看过滤器链

下面我们通过 Debug 断点来逐步验证整个过程。

第一站:DelegatingFilterProxy 接收请求

当有请求来到时,最先由DelegatingFilterProxy负责接收,因此在其doFilter()的首行打上断点。启动项目并访问http://localhost:8080/hello,程序首先跳转到该断点上。此时delegate还是 null,继续执行可以看到它最终被赋值一个FilterChainProxy的实例。

DelegatingFilterProxy是 Spring 整合 Servlet Filter 的桥接器:它本身是一个 Servlet 过滤器,但真正的过滤逻辑在 Spring 容器中的 bean 里,它通过 bean 名(springSecurityFilterChain)懒加载找到目标过滤器再委派执行。

第二站:FilterChainProxy 与 VirtualFilterChain

DelegatingFilterProxy会将请求委派给FilterChainProxy进行处理,在FilterChainProxy.doFilter()的首行打上断点。FilterChainProxy会在doFilterInternal()中生成一个内部类VirtualFilterChain的实例,用其调用 Spring Security 的整条过滤器链,在VirtualFilterChain.doFilter()的首行打上断点。

VirtualFilterChain会通过内部游标currentPosition依次调用存放在additionalFilters列表中的过滤器。整个调用链为:DelegatingFilterProxyFilterChainProxyVirtualFilterChain→ 各安全过滤器。

第三站:UsernamePasswordAuthenticationFilter —— requiresAuthentication()

接着程序跳转到AbstractAuthenticationProcessingFilterUsernamePasswordAuthenticationFilter的父类)的doFilter()中,通过requiresAuthentication()判定当前请求是否为登录提交。由于对/hello的首次访问是 GET 请求,判定结果为 false,过滤器直接放行,不执行认证逻辑。

第四站:DefaultLoginPageGeneratingFilter —— isLoginUrlRequest()

程序跳转到DefaultLoginPageGeneratingFilter.doFilter()中,通过isLoginUrlRequest()判断请求路径是否是/login。由于当前请求是/hello,判定结果为 false,过滤器放行。

第五站:AnonymousAuthenticationFilter —— 匿名 Token 的诞生

程序跳转到AnonymousAuthenticationFilter.doFilter()中。由于是首次请求,此时SecurityContextHolder.getContext().getAuthentication()为 null,因此过滤器会生成一个AnonymousAuthenticationToken的实例放入SecurityContextHolder,代表当前用户是一个匿名用户。

第六站:ExceptionTranslationFilter —— 异常捕获者

程序跳转到ExceptionTranslationFilter.doFilter()中。它的职责是捕获FilterSecurityInterceptor抛出的安全异常,我们在其 catch 代码块的首行打上断点,等待后续异常到来。

第七站:FilterSecurityInterceptor —— 授权决策

程序跳转到FilterSecurityInterceptor.doFilter()中,依次执行代码后,程序停留在其父类AbstractSecurityInterceptorattemptAuthorization()中。这里的关键是accessDecisionManager——即AccessDecisionManager(访问决策器)的实例。

AccessDecisionManager主要有 3 个实现类:

实现类策略说明
AffirmativeBased一票通过只要有一个投票器同意即放行,Spring Security 默认采用
ConsensusBased少数服从多数依据投票结果取多数意见
UnanimousBased一票否决所有投票器都同意才放行

此时AccessDecisionManager的实现类是AffirmativeBased,程序进入其decide()方法。决策的关键在voter.vote(authentication, object, configAttributes)这句代码上——由各个AccessDecisionVoter投票器对当前认证信息投票。

决策落定:AuthenticationTrustResolverImpl.isAnonymous()

通过跟踪调试,程序最终进入AuthenticationTrustResolverImpl.isAnonymous()中。isAssignableFrom()判断前者是否是后者的父类:anonymousClass被固定为AnonymousAuthenticationToken.class,而参数authentication由前面AnonymousAuthenticationFilter可知正是AnonymousAuthenticationToken的实例,因此isAnonymous()返回 true——即当前用户是匿名用户,没有访问/hello的权限。

异常抛出:AccessDeniedException

判定为匿名后,FilterSecurityInterceptor抛出AccessDeniedException异常,程序返回到ExceptionTranslationFilter的 catch 块中。

回到 ExceptionTranslationFilter:入口点的选择

ExceptionTranslationFilter中,handleSpringSecurityException()会区分异常类型:AuthenticationException(认证异常)与AccessDeniedException(访问拒绝异常)。匿名用户访问受保护资源触发的是后者,但 Spring Security 的处理策略是:如果SecurityContextHolder中没有认证信息(或只有匿名认证信息),则按未认证处理,启动认证流程。

程序会依次进入DelegatingAuthenticationEntryPointLoginUrlAuthenticationEntryPoint中。DelegatingAuthenticationEntryPoint会按RequestMatcher逐一匹配已注册的入口点,没有匹配项时使用默认入口点。最后由LoginUrlAuthenticationEntryPoint.commence()决定重定向到/login,同时在重定向前,ExceptionTranslationFilter.sendStartAuthentication()会清空SecurityContextHolder并通过requestCache.saveRequest()保存原始请求,以便认证成功后回跳。

源码佐证ExceptionTranslationFilter的异常分流与sendStartAuthentication()的调用链,在 SpringSecurity 流程补充 中有完整的源码片段,包括DelegatingAuthenticationEntryPoint.commence()遍历entryPoints匹配并执行对应入口点的逻辑。

登录流程:从 login.html 到 /hello

后续对/login的请求同样会经过之前的执行流程。

GET /login:返回登录页面

DefaultLoginPageGeneratingFilter.doFilter()中,通过isLoginUrlRequest()判定为 true(请求路径是/login),直接返回login.html——也就是我们在开头看到的登录页面。在默认配置下,该过滤器会在内存中动态生成一个包含 Username、Password 输入框和 Sign in 按钮的简单 HTML 登录表单。

POST /login:UsernamePasswordAuthenticationFilter 认证

当我们输入用户名和密码,点击Sign in,程序来到AbstractAuthenticationProcessingFilter.doFilter()中,通过requiresAuthentication()判定为 true(POST 请求且路径匹配登录提交路径),因此交给其子类UsernamePasswordAuthenticationFilter处理。

UsernamePasswordAuthenticationFilter会从请求参数中取出用户名和密码,封装成一个UsernamePasswordAuthenticationToken的实例,然后交给AuthenticationManager(实际由ProviderManager及其持有的AuthenticationProvider链,如DaoAuthenticationProvider)进行校验。校验通过后会将请求重定向到我们一开始请求的路径/hello(该路径正是此前被requestCache保存下来的原始请求)。

源码佐证:默认的认证入口与校验链路在 SpringSecurity 自定义用户认证 中有完整 Debug 追踪:UsernamePasswordAuthenticationFilterAuthenticationManagerProviderManager.authenticate()AbstractUserDetailsAuthenticationProvider.authenticate()DaoAuthenticationProvider.retrieveUser()→ 最终调用UserDetailsService.loadUserByUsername()获取用户信息。这也解释了为什么实现自定义UserDetailsService即可接管用户信息来源。

认证成功后:回到 /hello

后续对/hello的请求经过过滤器链时就可以一路开绿灯——SecurityContextHolder中已存在认证信息,不再生成匿名 Token,FilterSecurityInterceptor的授权决策通过,请求最终交由HelloController返回 "Hello World"。

纵览:安全过滤器链在 Spring Boot 中是如何被装配起来的

请求全过程解析清楚了,还有一个关键问题:这条过滤器链到底是谁、在什么时候装配起来的?这部分结合仓库内的源码笔记做一次纵向串联。

三个自动配置类

引入spring-boot-starter-security后,Spring Boot 会通过自动装配(AutoConfiguration.imports)加载一组安全相关的配置类,核心是三个:

  • SecurityAutoConfiguration:Spring Security 的自动配置入口,通过@EnableConfigurationProperties(SecurityProperties.class)绑定security.usersecurity.filter等配置项,并注册DefaultAuthenticationEventPublisher(认证成功/失败事件的发布订阅器);
  • UserDetailsServiceAutoConfiguration:在没有自定义UserDetailsService/AuthenticationProvider/AuthenticationManager时,自动装配内存版的InMemoryUserDetailsManager,并生成默认用户user与随机密码(即控制台打印的那条日志);
  • SecurityFilterAutoConfiguration:注册DelegatingFilterProxyRegistrationBean,把名为springSecurityFilterChain的过滤器以Order = -100DispatcherType覆盖ASYNC/ERROR/REQUEST的方式注册进 Servlet 容器。

@EnableWebSecurity 与 springSecurityFilterChain

WebSecurityEnablerConfiguration为容器添加了@EnableWebSecurity注解,这是 Spring Security 的核心注解。它@ImportWebSecurityConfigurationHttpSecurityConfiguration等配置类:

  • WebSecurityConfiguration负责创建WebSecurity并聚合所有SecurityConfigurer(如WebSecurityConfigurerAdapter),其springSecurityFilterChain()方法最终通过webSecurity.build()构建出名为springSecurityFilterChainFilterChainProxybean;
  • HttpSecurityConfiguration创建原型作用域的HttpSecurity,默认装配CsrfConfigurerExceptionHandlingConfigurerAnonymousConfigurerDefaultLoginPageConfigurerLogoutConfigurer等一系列SecurityConfigurer,并在doBuild()init()/configure()阶段把它们转换成实际的过滤器加入链中。

DelegatingFilterProxyRegistrationBean → FilterChainProxy → SecurityFilterChain

整个装配的依赖关系可以浓缩为一条链:

DelegatingFilterProxyRegistrationBean → FilterChainProxy → SecurityFilterChain
  • DelegatingFilterProxyRegistrationBean通过targetBeanName(默认值springSecurityFilterChain)从容器中找到FilterChainProxy并注册为 Servlet 过滤器,完成 Spring 过滤器与 Servlet 容器(如内嵌 Tomcat)的整合;
  • FilterChainProxy持有若干SecurityFilterChain(默认只有一条),并根据请求选择匹配的链;
  • SecurityFilterChain接口只有两个方法:boolean matches(HttpServletRequest request)判断该链是否匹配当前请求,List<Filter> getFilters()返回匹配后要执行的过滤器列表——这正是我们在 Debug 中看到的VirtualFilterChain.additionalFilters的来源。

当存在多个SecurityFilterChainbean 时(例如按@Order配置的多种安全规则),FilterChainProxy会按顺序用matches()匹配,命中即执行该链上的过滤器。

自定义安全配置的两种方式

如果你希望脱离默认配置,Spring Security 提供了两条路径:

  1. 声明SecurityFilterChainBean(Spring Security 5.x 推荐,Spring Boot 2.7 后WebSecurityConfigurerAdapter已废弃):
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.formLogin() // 启用表单登录 .loginPage("/login.html") // 自定义登录页 .loginProcessingUrl("/login") // 登录认证路径(对应表单 action) .and() .authorizeRequests() .antMatchers("/login.html", "/css/**", "/error").permitAll() // 免认证路径 .anyRequest().authenticated() // 其余所有请求都需要认证 .and().csrf().disable(); return http.build(); }
  1. 继承WebSecurityConfigurerAdapter(旧版方式):通过configure(HttpSecurity http)做同样的配置,其原理在 SpringSecurity 自定义用户认证 中有从AbstractConfiguredSecurityBuilder.doBuild()WebSecurityConfiguration.setFilterChainProxySecurityConfigurer()AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()的完整 Debug 追踪。

自定义登录页、UserDetailsService、登录成功/失败处理等扩展点,均可在 SpringSecurity 自定义用户认证 中找到可运行的改造示例。

小结

一次普通 HTTP 请求经过 Spring Security 的完整生命周期可以概括为:

  1. 接收DelegatingFilterProxy作为桥接器接收请求,懒加载并委派给FilterChainProxy
  2. 调度FilterChainProxy通过内部类VirtualFilterChaincurrentPosition游标依次执行过滤器链;
  3. 过滤UsernamePasswordAuthenticationFilter(登录提交判定)→DefaultLoginPageGeneratingFilter(登录页判定)→AnonymousAuthenticationFilter(匿名身份)→ExceptionTranslationFilter(异常捕获)→FilterSecurityInterceptor(授权决策);
  4. 决策FilterSecurityInterceptor委托AccessDecisionManager(默认AffirmativeBased一票通过)中的各投票器决策,匿名用户被AuthenticationTrustResolverImpl.isAnonymous()判定无权限;
  5. 引导认证ExceptionTranslationFilter捕获AccessDeniedException,经DelegatingAuthenticationEntryPointLoginUrlAuthenticationEntryPoint重定向到/login,同时requestCache保存原始请求;
  6. 认证:POST/loginUsernamePasswordAuthenticationFilter封装UsernamePasswordAuthenticationToken并校验,成功后重定向回原始路径,后续请求在过滤器链中一路放行直达业务 Controller。

掌握这条链路,也就掌握了 Spring Security 的骨架:无论是自定义登录页、接入UserDetailsService、配置方法级授权(@EnableGlobalMethodSecurity),还是排查"为什么我的接口被拦截/放行"这类问题,都可以回到这条过滤器链上定位。更完整的配置加载原理可继续阅读本仓库的 SpringSecurity 流程补充 与 SpringSecurity 自定义用户认证。

【免费下载链接】source-code-hunter😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 7:02:44

Spring全家桶核心技术解析与最佳实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 7:01:07

Pietra-Ricci指数在频谱感知中的创新应用与Matlab实现

1. 项目概述&#xff1a;Pietra-Ricci指数在频谱感知中的跨界应用Pietra-Ricci指数&#xff08;PRI&#xff09;这个原本活跃在经济不平等性分析领域的指标&#xff0c;最近被我们团队成功移植到了无线通信的频谱感知场景。这种跨界融合产生的Pietra-Ricci指数检测器&#xff0…

作者头像 李华
网站建设 2026/9/13 6:58:11

Rust+Tauri开源视频剪辑器WolfCut技术深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 6:54:40

风电并网下分布式动态状态估计技术解析与应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 6:54:24

群晖NAS无公网IP远程访问:cpolar内网穿透固定二级子域名配置教程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华