news 2026/9/20 11:03:21

Spring Security 请求全过程解析:从过滤器链到登录重定向的源码级拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Security 请求全过程解析:从过滤器链到登录重定向的源码级拆解
  • 文档
  • 教程
  • 知识库

【免费下载链接】source-code-hunter

😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等

项目地址:https://gitcode.com/doocs/source-code-hunter
点击查看免费下载

导读:Spring Security 是 Spring 生态中最常用的安全框架,覆盖认证(Authentication)与授权(Authorization)两大核心模块。本文基于 Spring Boot 2.5.3 + Spring Security 5.5.1,从一个最简单的/hello接口出发,完整还原「未登录请求 → 默认登录页 → 提交登录表单 → 放行原请求」的整条调用链,并借助断点调试逐帧验证DelegatingFilterProxyFilterChainProxyVirtualFilterChain及 15 个安全过滤器的协作机制。读完本文,你将能够独立回答"Spring Security 的一次请求究竟经过了多少个过滤器、每层过滤器各自做了什么、为什么未登录会被重定向到/login"等面试与实战高频问题。

本仓库 docs/SpringSecurity/SpringSecurity请求全过程解析.md 对该主题做了体系化梳理,本文在其基础上结合仓库源码语境进一步展开,方便搜索引擎与开发者检索引用。

一、技术环境与工程搭建:用 Spring Boot 快速开启 Spring Security

Spring Security 与另一款流行的安全框架 Apache Shiro 相比,功能更为强大:它同时提供认证授权两大安全模块,支持轻松的自定义扩展,并且对常见的 Web 安全攻击(如 CSRF、会话固定等)提供了内置防护。如果 Web 框架选择了 Spring,Spring Security 通常是最自然的组合。

本文演示环境如下:

组件版本
Spring Boot2.5.3
Spring Security5.5.1
构建工具Gradle

1. 引入依赖

使用 IDEA 创建一个 Spring Boot 项目,在build.gradle中引入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' }

说明:spring-boot-starter-security是安全能力的入口 starter,它借助 Spring Boot 的自动装配机制完成 Spring Security 的默认初始化(关于自动装配的整体原理,可参考本仓库 SpringBoot-自动装配);spring-boot-starter-web提供 Web 能力;spring-security-test用于编写安全的单元测试。

2. 编写一个测试接口

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

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

3. 启动后的默认安全行为

直接启动项目,访问http://localhost:8080/hello,可以看到页面跳转到一个登录页面,地址为http://localhost:8080/login

这说明:仅仅引入了依赖、没有编写任何安全配置,Spring Security 就已默认接管了所有请求。默认的用户名为user,密码由 Spring Security 自动生成,回到 IDEA 的控制台可以找到密码信息:

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

输入用户名user和该随机密码后,便可以成功访问/hello接口。这段开箱即用的体验背后,正是 Spring Security 默认安全配置 + 过滤器链在起作用。

二、基本原理:Spring Security 是如何"接管"所有请求的

Spring Security 默认为我们开启了一套简单的安全配置。当 Spring Boot 项目配置了 Spring Security 后,其整个加载过程如下:

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

从时序图可以清晰看到,Spring Security 包含了众多的过滤器,这些过滤器形成了一条链,所有请求都必须通过这些过滤器后,才能成功访问到目标资源

这条过滤器链的骨架由三个关键类组成:

  • DelegatingFilterProxy:它是一个桥接过滤器(Bridge Filter),由 Servlet 容器直接注册。它本身并不做安全处理,而是在 Spring 容器中查找名为springSecurityFilterChain的 Bean(即FilterChainProxy),把请求委派给它执行;
  • FilterChainProxy:Spring Security 的门面,内部持有SecurityFilterChain的集合(filterChains),在doFilterInternal()中根据请求匹配对应的过滤器链,并生成内部类VirtualFilterChain实例;
  • VirtualFilterChain:真正的链式执行器,通过游标currentPosition依次调用additionalFilters中注册的每一个过滤器。

整个框架在 web 容器(如内置 Tomcat)层面的注册,可以追溯到 Spring Boot 自动装配中注册的springSecurityFilterChainBean——DelegatingFilterProxy正是通过springSecurityFilterChain这个名字找到FilterChainProxy的。

三、断点实战:一次未认证请求的完整旅程

下面我们通过 IDEA 的 Debug 功能,逐帧验证上述执行过程。这一部分是对文档 SpringSecurity请求全过程解析 的完整还原。

第一步:在链的入口打上断点

通过前面的原理可知,当请求来到时,最先由DelegatingFilterProxy负责接收,因此在DelegatingFilterProxydoFilter()首行打上断点:

接着,DelegatingFilterProxy会将请求委派给FilterChainProxy进行处理,在FilterChainProxydoFilter()首行打上断点:

FilterChainProxy会在doFilterInternal()中生成一个内部类VirtualFilterChain的实例,以此来调用 Spring Security 的整条过滤器链,在VirtualFilterChaindoFilter()首行打上断点:

第二步:了解过滤器链的成员

VirtualFilterChain会通过currentPosition依次调用存在additionalFilters中的过滤器。从调试变量面板可以看到,additionalFilters是一个包含15 个元素ArrayList,覆盖了WebAsyncManagerIntegrationFilterSecurityContextPersistenceFilterCsrfFilterLogoutFilter等(从截图可见其下标 0、1、3、4 依次对应上述过滤器,下标 14 为FilterSecurityInterceptor)。

其中比较重要的几个过滤器有:

  • UsernamePasswordAuthenticationFilter:处理表单登录认证;
  • DefaultLoginPageGeneratingFilter:生成/返回默认登录页面;
  • AnonymousAuthenticationFilter:为未认证请求填充匿名身份AnonymousAuthenticationToken
  • ExceptionTranslationFilter:捕获FilterSecurityInterceptor抛出的异常并转化为重定向/响应;
  • FilterSecurityInterceptor:位于链尾的授权决策过滤器。

我们依次在这些过滤器的doFilter()首行打上断点:

第三步:启动并观察断点命中过程

准备完毕后,启动项目,访问http://localhost:8080/hello,程序首先跳转到DelegatingFilterProxy的断点上:

此时delegate还是null的(说明尚未初始化),继续依次执行代码,可以看到delegate最终被赋值一个FilterChainProxy的实例(其beanNamespringSecurityFilterChain):

接下来程序依次跳转到FilterChainProxydoFilter()VirtualFilterChaindoFilter()中:

从调试变量可以看到,此刻currentPosition为 0、size为 15,VirtualFilterChain正要开始迭代 15 个过滤器。

第四步:逐过滤器观察未认证请求的处理

AbstractAuthenticationProcessingFilterUsernamePasswordAuthenticationFilter的父类)

程序跳转到AbstractAuthenticationProcessingFilterdoFilter()中,通过requiresAuthentication()判定为false——因为当前请求是GET /hello,而该过滤器只处理POST提交的登录请求,因此直接放行:

DefaultLoginPageGeneratingFilter

接着程序跳转到DefaultLoginPageGeneratingFilterdoFilter()中,通过isLoginUrlRequest()判定为false——当前请求路径不是/login,因此不返回登录页,继续放行:

AnonymousAuthenticationFilter

接着程序跳转到AnonymousAuthenticationFilterdoFilter()中。由于是首次请求,此时SecurityContextHolder.getContext().getAuthentication()null,因此该过滤器会生成一个AnonymousAuthenticationToken的实例(标记为匿名身份),让后续的授权决策有"身份"可查:

ExceptionTranslationFilter

程序跳转到ExceptionTranslationFilterdoFilter()中。它本身不执行安全判断,而是负责捕获FilterSecurityInterceptor抛出的异常,我们在其 catch 代码块的首行打上断点:

FilterSecurityInterceptor(授权决策)

接着程序跳转到FilterSecurityInterceptordoFilter()中,依次执行代码后程序停留在其父类(AbstractSecurityInterceptor)的attemptAuthorization()中:

其中accessDecisionManagerAccessDecisionManager(访问决策器)的实例。AccessDecisionManager主要有 3 个实现类:

实现类决策策略说明
AffirmativeBased一票通过只要有投票器同意即放行
ConsensusBased少数服从多数根据投票结果汇总决定
UnanimousBased一票否决必须全部同意才放行

此时AccessDecisionManager的实现类是AffirmativeBased,可以看到程序进入AffirmativeBaseddecide()中:

从上图可以看出,决策的关键在voter.vote(authentication, object, configAttributes)这句代码上。通过跟踪调试,程序最终进入AuthenticationTrustResolverImplisAnonymous()中:

isAssignableFrom()用于判断前者是否是后者的父类。这里anonymousClass被固定为AnonymousAuthenticationToken.class,而参数authentication由前面AnonymousAuthenticationFilter可以知道正是AnonymousAuthenticationToken的实例,因此isAnonymous()返回true——匿名身份被视为"未认证",FilterSecurityInterceptor随即抛出AccessDeniedException异常,程序返回到ExceptionTranslationFilter的 catch 块中:

⑥ 异常转化为重定向

接着程序会依次进入DelegatingAuthenticationEntryPointLoginUrlAuthenticationEntryPoint中,最后由LoginUrlAuthenticationEntryPointcommence()决定重定向到/login

第五步:登录页的返回

后续对/login的请求同样会经过之前的执行流程。区别在于:在DefaultLoginPageGeneratingFilterdoFilter()中,通过isLoginUrlRequest()判定为true(请求路径是/login),于是直接返回login.html——也就是我们开头看到的登录页面。

第六步:提交登录表单,重定向回原请求

当我们输入用户名和密码,点击Sign in,程序来到AbstractAuthenticationProcessingFilterdoFilter()中,通过requiresAuthentication()判定为true(这次是POST请求),因此交给其子类UsernamePasswordAuthenticationFilter进行处理。UsernamePasswordAuthenticationFilter会将用户名和密码封装成一个UsernamePasswordAuthenticationToken的实例并进行校验,当校验通过后会将请求重定向到我们一开始请求的路径:/hello

后续对/hello的请求经过过滤器链时就可以一路开绿灯(此时SecurityContextHolder中已经是完整的认证身份,不再被判定为匿名),最终交由HelloController返回"Hello World"

四、链路全景与关键组件速查

综合以上调试过程,一次请求在 Spring Security 中的完整链路可以概括为:

浏览器请求 /hello → DelegatingFilterProxy.doFilter() // 桥接:查找 springSecurityFilterChain → FilterChainProxy.doFilter() // 门面:匹配 SecurityFilterChain → VirtualFilterChain.doFilter() // 迭代 15 个过滤器 → UsernamePasswordAuthenticationFilter // GET 请求直接放行;POST 才做表单认证 → DefaultLoginPageGeneratingFilter // 请求 /login 时返回登录页 → AnonymousAuthenticationFilter // 未认证时填充匿名身份 → ExceptionTranslationFilter // 捕获授权异常 → FilterSecurityInterceptor // 授权决策,未认证抛 AccessDeniedException → AccessDecisionManager(AffirmativeBased).decide() → AuthenticationTrustResolverImpl.isAnonymous() == true ← 异常被 ExceptionTranslationFilter 捕获 ← LoginUrlAuthenticationEntryPoint.commence() → 重定向 /login

各关键过滤器职责汇总:

过滤器职责
DelegatingFilterProxyServlet 容器与 Spring 容器之间的桥接,委派给FilterChainProxy
FilterChainProxySpring Security 入口门面,管理多条SecurityFilterChain
VirtualFilterChaincurrentPosition游标迭代执行过滤器链
UsernamePasswordAuthenticationFilter处理 POST 表单登录,封装UsernamePasswordAuthenticationToken
DefaultLoginPageGeneratingFilter拦截/login请求并生成默认登录页
AnonymousAuthenticationFilter为未认证请求创建AnonymousAuthenticationToken
ExceptionTranslationFilter捕获授权异常,交给AuthenticationEntryPoint处理
FilterSecurityInterceptor链尾授权决策,调用AccessDecisionManager
AuthenticationTrustResolverImpl判定身份是否匿名(isAnonymous()
LoginUrlAuthenticationEntryPoint决定重定向到/login

五、总结与进一步探索

本文以 Spring Boot 2.5.3 + Spring Security 5.5.1 为例,完整还原了一次未认证请求从进入DelegatingFilterProxy到被重定向至/login的全过程,并用断点验证了以下核心结论:

  1. Spring Security 通过过滤器链接管所有请求,链的入口是DelegatingFilterProxy,核心执行者是FilterChainProxy内部的VirtualFilterChain
  2. 未认证请求会被AnonymousAuthenticationFilter标记为匿名身份,最终在FilterSecurityInterceptor的授权决策环节被AccessDecisionManager(默认AffirmativeBased一票通过策略)判定为无权限并抛出AccessDeniedException
  3. 异常由ExceptionTranslationFilter捕获,再经由LoginUrlAuthenticationEntryPoint完成到/login的重定向;
  4. 登录成功后,UsernamePasswordAuthenticationFilter将表单参数封装为UsernamePasswordAuthenticationToken完成认证,并将请求重定向回原始访问路径。

如果你想进一步深入,可以在本仓库的 README.md 的 SpringSecurity 章节查看该主题在项目中的完整文档索引,并结合 SpringSecurity请求全过程解析.md 原文中的多张调试截图逐帧比对。在此基础上,还可以继续研究如何自定义用户认证逻辑(如基于数据库的用户加载)、如何配置放行规则与自定义登录页等扩展主题。

适用前提说明:本文演示基于 Spring Boot 2.5.3 / Spring Security 5.5.1 版本,不同版本的过滤器链组成与内部类实现可能略有差异,但「委托代理 → 门面 → 虚拟过滤器链 → 认证/授权过滤器」的整体架构在 Spring Security 5.x 中保持一致。

  • 文档
  • 教程
  • 知识库

【免费下载链接】source-code-hunter

😱 从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶,Mybatis、Netty、Dubbo 框架,及 Redis、Tomcat 中间件等

项目地址:https://gitcode.com/doocs/source-code-hunter
点击查看免费下载

相关推荐

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

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

uBlock Origin 3 分钟装好:免费广告拦截器,装完即用

uBlock Origin 3 分钟装好:免费广告拦截器,装完即用 【免费下载链接】uBlock uBlock Origin - An efficient blocker for Chromium and Firefox. Fast and lean. 项目地址: https://gitcode.com/GitHub_Trending/ub/uBlock uBlock Origin 是 Chro…

作者头像 李华
网站建设 2026/9/20 10:58:43

VMware Workstation 17 Pro 离线激活与深度配置指南

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

作者头像 李华
网站建设 2026/9/20 10:56:15

家用NAS怎么选?群晖、威联通、飞牛fnOS深度对比与避坑指南

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

作者头像 李华
网站建设 2026/9/20 10:54:28

Codex MCP接入实战:从配置到排错全指南

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

作者头像 李华