资讯动态

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

发布时间:2026/9/13 7:05:18 来源:尧图企业网站定制
Spring Security 请求全过程解析从 DelegatingFilterProxy 到 FilterSecurityInterceptor 的过滤器链源码追踪【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunterSpring 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.3Spring Security 版本5.5.1。使用 IDEA 创建一个 Spring Boot 项目后在构建脚本中引入spring-boot-starter-securitydependencies { 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所有请求都必须通过这些过滤器后才能成功访问到资源。一次典型的未认证访问会经历GET /hello请求进入过滤器链UsernamePasswordAuthenticationFilter判定不是登录提交非 POST 的/loginDefaultLoginPageGeneratingFilter判定不是登录页请求AnonymousAuthenticationFilter生成匿名认证 Token最终FilterSecurityInterceptor判定匿名用户无权限抛出AccessDeniedException请求被重定向到/loginGET /loginDefaultLoginPageGeneratingFilter判定是登录页请求直接返回登录页面POST /loginUsernamePasswordAuthenticationFilter判定是登录提交封装并校验用户名密码认证通过后重定向回原请求路径/helloGET /hello认证后过滤器链一路放行请求最终到达HelloController返回 hello world。过滤器链的核心成员请求执行链路中比较重要的几个过滤器均位于FilterChainProxy的过滤器链内有过滤器核心职责关键判定方法UsernamePasswordAuthenticationFilter拦截登录提交封装用户名密码并认证requiresAuthentication()是否匹配登录提交POST 请求DefaultLoginPageGeneratingFilter生成默认登录页面isLoginUrlRequest()请求路径是否为/loginAnonymousAuthenticationFilter为未认证请求创建匿名身份检查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 与 VirtualFilterChainDelegatingFilterProxy会将请求委派给FilterChainProxy进行处理在FilterChainProxy.doFilter()的首行打上断点。FilterChainProxy会在doFilterInternal()中生成一个内部类VirtualFilterChain的实例用其调用 Spring Security 的整条过滤器链在VirtualFilterChain.doFilter()的首行打上断点。VirtualFilterChain会通过内部游标currentPosition依次调用存放在additionalFilters列表中的过滤器。整个调用链为DelegatingFilterProxy→FilterChainProxy→VirtualFilterChain→ 各安全过滤器。第三站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()中依次执行代码后程序停留在其父类AbstractSecurityInterceptor的attemptAuthorization()中。这里的关键是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中没有认证信息或只有匿名认证信息则按未认证处理启动认证流程。程序会依次进入DelegatingAuthenticationEntryPoint、LoginUrlAuthenticationEntryPoint中。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 /loginUsernamePasswordAuthenticationFilter 认证当我们输入用户名和密码点击Sign in程序来到AbstractAuthenticationProcessingFilter.doFilter()中通过requiresAuthentication()判定为 truePOST 请求且路径匹配登录提交路径因此交给其子类UsernamePasswordAuthenticationFilter处理。UsernamePasswordAuthenticationFilter会从请求参数中取出用户名和密码封装成一个UsernamePasswordAuthenticationToken的实例然后交给AuthenticationManager实际由ProviderManager及其持有的AuthenticationProvider链如DaoAuthenticationProvider进行校验。校验通过后会将请求重定向到我们一开始请求的路径/hello该路径正是此前被requestCache保存下来的原始请求。源码佐证默认的认证入口与校验链路在 SpringSecurity 自定义用户认证 中有完整 Debug 追踪UsernamePasswordAuthenticationFilter→AuthenticationManager→ProviderManager.authenticate()→AbstractUserDetailsAuthenticationProvider.authenticate()→DaoAuthenticationProvider.retrieveUser()→ 最终调用UserDetailsService.loadUserByUsername()获取用户信息。这也解释了为什么实现自定义UserDetailsService即可接管用户信息来源。认证成功后回到 /hello后续对/hello的请求经过过滤器链时就可以一路开绿灯——SecurityContextHolder中已存在认证信息不再生成匿名 TokenFilterSecurityInterceptor的授权决策通过请求最终交由HelloController返回 Hello World。纵览安全过滤器链在 Spring Boot 中是如何被装配起来的请求全过程解析清楚了还有一个关键问题这条过滤器链到底是谁、在什么时候装配起来的这部分结合仓库内的源码笔记做一次纵向串联。三个自动配置类引入spring-boot-starter-security后Spring Boot 会通过自动装配AutoConfiguration.imports加载一组安全相关的配置类核心是三个SecurityAutoConfigurationSpring Security 的自动配置入口通过EnableConfigurationProperties(SecurityProperties.class)绑定security.user、security.filter等配置项并注册DefaultAuthenticationEventPublisher认证成功/失败事件的发布订阅器UserDetailsServiceAutoConfiguration在没有自定义UserDetailsService/AuthenticationProvider/AuthenticationManager时自动装配内存版的InMemoryUserDetailsManager并生成默认用户user与随机密码即控制台打印的那条日志SecurityFilterAutoConfiguration注册DelegatingFilterProxyRegistrationBean把名为springSecurityFilterChain的过滤器以Order -100、DispatcherType覆盖ASYNC/ERROR/REQUEST的方式注册进 Servlet 容器。EnableWebSecurity 与 springSecurityFilterChainWebSecurityEnablerConfiguration为容器添加了EnableWebSecurity注解这是 Spring Security 的核心注解。它Import了WebSecurityConfiguration、HttpSecurityConfiguration等配置类WebSecurityConfiguration负责创建WebSecurity并聚合所有SecurityConfigurer如WebSecurityConfigurerAdapter其springSecurityFilterChain()方法最终通过webSecurity.build()构建出名为springSecurityFilterChain的FilterChainProxybeanHttpSecurityConfiguration创建原型作用域的HttpSecurity默认装配CsrfConfigurer、ExceptionHandlingConfigurer、AnonymousConfigurer、DefaultLoginPageConfigurer、LogoutConfigurer等一系列SecurityConfigurer并在doBuild()的init()/configure()阶段把它们转换成实际的过滤器加入链中。DelegatingFilterProxyRegistrationBean → FilterChainProxy → SecurityFilterChain整个装配的依赖关系可以浓缩为一条链DelegatingFilterProxyRegistrationBean → FilterChainProxy → SecurityFilterChainDelegatingFilterProxyRegistrationBean通过targetBeanName默认值springSecurityFilterChain从容器中找到FilterChainProxy并注册为 Servlet 过滤器完成 Spring 过滤器与 Servlet 容器如内嵌 Tomcat的整合FilterChainProxy持有若干SecurityFilterChain默认只有一条并根据请求选择匹配的链SecurityFilterChain接口只有两个方法boolean matches(HttpServletRequest request)判断该链是否匹配当前请求ListFilter getFilters()返回匹配后要执行的过滤器列表——这正是我们在 Debug 中看到的VirtualFilterChain.additionalFilters的来源。当存在多个SecurityFilterChainbean 时例如按Order配置的多种安全规则FilterChainProxy会按顺序用matches()匹配命中即执行该链上的过滤器。自定义安全配置的两种方式如果你希望脱离默认配置Spring Security 提供了两条路径声明SecurityFilterChainBeanSpring 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(); }继承WebSecurityConfigurerAdapter旧版方式通过configure(HttpSecurity http)做同样的配置其原理在 SpringSecurity 自定义用户认证 中有从AbstractConfiguredSecurityBuilder.doBuild()→WebSecurityConfiguration.setFilterChainProxySecurityConfigurer()→AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()的完整 Debug 追踪。自定义登录页、UserDetailsService、登录成功/失败处理等扩展点均可在 SpringSecurity 自定义用户认证 中找到可运行的改造示例。小结一次普通 HTTP 请求经过 Spring Security 的完整生命周期可以概括为接收DelegatingFilterProxy作为桥接器接收请求懒加载并委派给FilterChainProxy调度FilterChainProxy通过内部类VirtualFilterChain按currentPosition游标依次执行过滤器链过滤UsernamePasswordAuthenticationFilter登录提交判定→DefaultLoginPageGeneratingFilter登录页判定→AnonymousAuthenticationFilter匿名身份→ExceptionTranslationFilter异常捕获→FilterSecurityInterceptor授权决策决策FilterSecurityInterceptor委托AccessDecisionManager默认AffirmativeBased一票通过中的各投票器决策匿名用户被AuthenticationTrustResolverImpl.isAnonymous()判定无权限引导认证ExceptionTranslationFilter捕获AccessDeniedException经DelegatingAuthenticationEntryPoint→LoginUrlAuthenticationEntryPoint重定向到/login同时requestCache保存原始请求认证POST/login由UsernamePasswordAuthenticationFilter封装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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价