资讯动态

Spring Security 文档精读:Filter 链注册机制与 OAuth2 权限迁移

发布时间:2026/9/9 9:44:10 来源:尧图企业网站定制
Spring Security 官网文档我前前后后翻过不下十遍每次以为自己看懂了新项目一开始还是会踩坑。最典型的一次是排查过滤器不生效查了一周网上博客愣是没找到原因最后翻回官网 Architecture 那一章十分钟就定位到问题——原来是我对 DelegatingFilterProxy 的注册机制理解错了。从那以后我就养成了一个习惯遇到 Spring Security 的问题第一件事不是搜索而是打开官方文档对应章节。这篇东西不是文档翻译也不是教程复读而是从怎么读文档的角度出发把 Spring Security 官网文档里那些真正重要的部分串一遍重点说清楚三个热搜里反复出现的问题Spring Security 的中文文档怎么找、Filter 链到底是怎么完成注册的、OAuth2 里的 hasScope 为什么在新版本里找不到了。1. 为什么官网文档是学习 Spring Security 最好的地方1.1 搜索博客的碎片化困境Spring Security 的学习路径非常容易走偏。你在搜索引擎里输入Spring Security 登录认证能收到一堆博客文章但几乎每一篇都只讲了某个具体场景配置了 SecurityFilterChain、加了表单登录、放行了几个路径然后就没有然后了。这些博客本身没有错但 Spring Security 是一个高度抽象、分层极多的框架单独看任何一个配置片段都无法建立完整的认知。最典型的问题就是版本差异。Spring Boot 2.7 时代还在用authorizeRequests到了 Spring Boot 3.x 就直接让你用authorizeHttpRequests旧博客里全是WebSecurityConfigurerAdapter新版本里这个类已经彻底移除了。搜索引擎给你的结果往往混杂了三年前的写法和今年的最新 API新手根本分不清哪个是当前可用方案。官网文档恰好解决了这个问题。它只维护当前版本的内容旧版本单独存档章节之间有明确的逻辑递进而且每个配置项、每个过滤器的职责都有官方定义。学习成本确实比看博客高但换来的是准确性和系统性。1.2 官网文档的阅读路线图Spring Security 官方文档从 6.x 开始做了一个很清晰的结构划分。进入文档首页你应该首先注意这几个方向Servlet Applications基于 Spring MVC 的经典 Web 应用这是绝大多数开发者的主战场也是文档内容最丰富的一块。Reactive Applications基于 WebFlux 的响应式应用写法上和 Servlet 版本有较大差异。Getting Started快速上手示例用于建立第一印象。我的建议是第一次读文档不要直接扎进 Authentication 章节先花半小时把Architecture完整读一遍。这一章用图文方式解释了请求从进入 Servlet 容器到最后被业务代码处理的完整过滤链是整个框架的地基。地基没打牢之前看后面的内容都很容易飘。1.3 关于中文文档的正确打开方式网络上能搜到一些 Spring Security 的中文翻译文档但大部分翻译版本停留在 Spring Security 5.x 时代甚至还有更早的 4.x 内容。框架 API 变化频繁用旧文档指导新项目开发很容易掉坑。如果英文阅读吃力我推荐的做法是用浏览器翻译功能看原文。现代浏览器的整页翻译对技术文档的翻译质量已经相当不错术语基本保留翻译后仍然能看懂逻辑。关键 API 的 javadoc 注释最好对照原文看因为这些注释的措辞往往决定了方法的行为边界。另一个折中方案是先用中文快速浏览章节结构知道哪一章大概在讲什么再在需要深度理解时精读英文原版。1.4 版本选择如果你还想用老版本很多公司项目还在 Spring Boot 2.x 时代对应的 Spring Security 是 5.7/5.8 系列。这种情况不需要强行上 6.x 文档官网文档都提供了版本切换入口点开左侧导航栏底部的版本下拉框可以找到 5.7、5.8 的历史版本。老项目中如果有配置要查务必切到对应版本看否则会看到很多方法名对不上、语义完全不同的内容。2. 核心架构Spring Security 的 Filter 链是如何完成注册的Spring Security filter 是如何完成注册的这个搜索词在近期热度很高。我猜问这个问题的人十有八九都遇到过自定义 Filter 不生效、或者过滤链里多了一个自己没见过的过滤器这类诡异问题。这一节就针对 Filter 注册机制做个完整拆解。2.1 从 Servlet 过滤器到 DelegatingFilterProxySpring Security 在 Web 应用中的工作基础是 Servlet 规范中的 Filter。正常情况下一个过滤器要在 web.xml 或 Servlet 容器里注册容器收到请求后按照注册顺序执行过滤器。但 Spring Security 的设计目标是完全脱离容器配置不依赖 web.xml。为了让 Spring 容器里的 Bean 能够被 Servlet 容器调用Spring 提供了一个桥接过滤器DelegatingFilterProxy。这个过滤器本身会被注册到 Servlet 容器中但它只是一个空壳真正干活的时候它会去 Spring 容器里查找名字为springSecurityFilterChain的 Bean然后把请求委托给它。这里的关键点是DelegatingFilterProxy 是一个代理它自己不做任何安全校验它只负责从 ApplicationContext 里找目标 Bean 并转发请求。很多人在项目里自定义了一个 SecurityFilterChain 就以为万事大吉却没有考虑到这个 Bean 是否真的能被 Servlet 容器感知。2.2 谁是 springSecurityFilterChainFilterChainProxy 的秘密顺着 DelegatingFilterProxy 往下追目标 Bean 的名字是springSecurityFilterChain它的实际类型是FilterChainProxy。FilterChainProxy实现了 Filter 接口但它不是普通的过滤器它是一个过滤器链容器。这个类内部维护了一个有序的SecurityFilterChain列表每个 SecurityFilterChain 对应一个 RequestMatcher 和一组过滤器对象。请求进来时FilterChainProxy 会遍历列表找到第一个匹配当前请求的 SecurityFilterChain然后执行它内部维护的那组过滤器。换句话说你在配置类里写的Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth - auth.anyRequest().authenticated()) .formLogin(Customizer.withDefaults()); return http.build(); }这个filterChain方法返回的SecurityFilterChain对象会被框架收集起来和框架内置的其他 SecurityFilterChain比如保护错误页面、静态资源的那几条默认链一起在FilterChainProxy内部按顺序排列。2.3 Spring Boot 自动注册的关键环节在 Spring Boot 项目中DelegatingFilterProxy 也不是手动添加到 web.xml 里的而是由自动配置完成的。Spring Boot 的SecurityFilterAutoConfiguration会检测到容器中存在springSecurityFilterChain这个 Bean然后自动创建一个FilterRegistrationBean把 DelegatingFilterProxy 注册到 Servlet 容器中注册顺序默认是所有过滤器中最靠后的Ordered.LOWEST_PRECEDENCE - 1这样做的目的是确保请求先经过业务过滤器最后再进入 Spring Security 的过滤链。这里有一个容易让人犯迷糊的地方EnableWebSecurity 导入的配置类也会做初始化工作Spring Boot 的自动配置则负责把过滤器注册进容器。如果脱离了 Spring Boot比如纯 Spring 项目你需要手动在 AbstractSecurityWebApplicationInitializer 的子类里完成初始化它会替你完成 DelegatingFilterProxy 的注册。2.4 用日志验证过滤器链路是否注册成功注册是否成功最直接的办法是开启 DEBUG 日志logging.level.org.springframework.securityDEBUG启动项目后你会看到类似这样的日志输出DefaultSecurityFilterChain - Validated match pattern [/**] DefaultSecurityFilterChain - Adding security filter UsernamePasswordAuthenticationFilter DefaultSecurityFilterChain - Adding security filter ExceptionTranslationFilter DefaultSecurityFilterChain - Adding security filter AuthorizationFilter这组日志表明你的 SecurityFilterChain 已经组装完成并被 FilterChainProxy 管理。如果这里的过滤器数量和你配置的不一致比如少了某个自定义过滤器说明注册链路出了问题。2.5 常见的自定义 Filter 不生效的原因实际开发中最常见的自定义 Filter 不生效无非以下几种情况自定义过滤器实现了 Filter 接口但没有交给 Spring 容器管理。FilterChainProxy 只认 SecurityFilterChain 里添加的过滤器你通过 Component 注册的普通 Filter 走的是 Servlet 容器过滤器链两者是并行的不是你直觉中的串行。在 HttpSecurity 里用 addFilterBefore 添加了过滤器但方法参数类型不匹配。addFilterBefore要求传入的过滤器必须实现jakarta.servlet.Filter而且这里的before指的是在 SecurityFilterChain 内部顺序中的 before不是 Servlet 容器过滤器链的顺序。多个 SecurityFilterChain 时自定义过滤器加到了错误的那条链上。加在哪条链取决于你是在哪个SecurityFilterChain实例的 HttpSecurity 上 add 的。如果请求匹配了其他链那你的过滤器根本不会执行。过滤器注册顺序不对。FilterRegistrationBean的 order 属性会被所有过滤器使用而 Spring Security 默认用最低优先级注册。如果不小心给了自定义 Filter 一个更高优先级数值更小请求还没进安全链就被拦截了。在动手改代码之前先想清楚这个过滤器到底应该在 Servlet 容器层执行还是在 Spring Security 的 SecurityFilterChain 内执行这个问题的答案直接决定了你的注册方式。3. 官网文档里认证与授权章节的正确打开方式3.1 Authentication 章节的阅读逻辑Spring Security 官方文档的Authentication章节按我说的体系阅读法拆开看是这样一条线先理解AuthenticationManager是总入口接口的authenticate方法接收一个Authentication对象返回一个已认证的Authentication对象。ProviderManager是 AuthenticationManager 的默认实现它维护了一组AuthenticationProvider逐个尝试去认证请求。每个AuthenticationProvider只处理一种特定的凭证类型。DaoAuthenticationProvider是其中最常见的一个它会通过UserDetailsService加载用户信息然后对密码做比对密码编码器由PasswordEncoder负责。整条链路可以用一句话串起来登录请求来了AuthenticationManager 找到合适的 AuthenticationProviderProvider 从 UserDetailsService 拿用户数据比对凭证比对通过就返回完整 Authentication 对象。之后通过 SecurityContextHolder 把它保存在当前线程的 SecurityContext 里。3.2 授权部分的三个层级授权是 Spring Security 里最容易产生命名混乱的部分因为授权可以发生在三个完全不同的层级URL 级授权用authorizeHttpRequests配置针对路径做控制。比如/admin/**只有 ADMIN 角色能访问。方法级授权在 Service 或 Controller 方法上打注解比如PreAuthorize(hasRole(ADMIN))这是最常用、最灵活的方式。对象级授权通过 ACL 模块实现控制到单条数据记录的读写权限非常重量级大多数业务场景用不到。新版本的文档在 Authorization 章节里会按这个层次展开。对绝大多数项目你只需要重点看 URL 级和方法级就够了。3.3 EnableMethodSecurity 时代的方法安全方法安全这块5.6 之前的老写法是用EnableGlobalMethodSecurity从 5.6 开始官方引入EnableMethodSecurity作为替代。6.x 版本里已经找不到EnableGlobalMethodSecurity了。EnableMethodSecurity提供了三个可选项EnableMethodSecurity(jsr250Enabled true, securedEnabled true)securedEnabled true启用Secured注解写法是Secured(ROLE_ADMIN)。jsr250Enabled true启用 JSR-250 规范注解比如RolesAllowed。默认启用的PreAuthorize/PostAuthorize不受这两个开关控制它一直生效。实际开发中PreAuthorize(hasAuthority(USER_DELETE))这种写法最灵活它能直接写 SpEL 表达式可以调用方法参数、做逻辑组合。如果你的项目只需要简单的角色判断Secured也够用。但需要注意的是在 SecurityFilterChain 中hasRole(ADMIN)相当于hasAuthority(ROLE_ADMIN)hasRole只是自动帮你加了ROLE_前缀的语法糖。3.4 认证授权章节最容易读漏的几个点官网文档里有些知识点不在显眼的标题下面但实战非常关键CSRF 保护Spring Security 默认开启 CSRF 防护REST API 如果走会话认证必须携带 CSRF Token否则 POST 请求会被拒。用 JWT 的接口通常不需要 CSRF因为天然免疫这种攻击。Session 管理SessionCreationPolicy的控制对无状态服务很重要STATELESS模式下 Spring Security 不会再创建 Session但仍然会尝试从请求头里解析 Bearer Token。异常处理ExceptionTranslationFilter是框架处理认证/授权异常的枢纽它会把AccessDeniedException和AuthenticationException转成 HTTP 响应或重定向。自定义 401/403 返回体时你要处理的其实是这个过滤器之后的AuthenticationEntryPoint和AccessDeniedHandler。4. OAuth2 文档的困惑hasScope 方法去哪了4.1 让无数人困惑的往事Spring Security OAuth2 没有 hasScope 方法了吗——这个坑太经典了。在 Spring Security 5.x 的早期版本5.7 及以前OAuth2 资源服务器配置里确实有hasScope()方法。因为网上的教程和许多开源项目都是那个时期写的所以搜索出来的代码大部分是这种写法http.oauth2ResourceServer(oauth2 - oauth2 .jwt(jwt - jwt .jwtAuthenticationConverter(jwtAuthenticationConverter()) ) .accessDeniedHandler(...) ); PreAuthorize(hasScope(read))这种写法在 Spring Security 5.7 确实是合法的。但从 5.8 开始官方把hasScope和hasAnyScope标记为 deprecated到了 6.x 直接移除了。所以你现在打开新项目的依赖看到的基础是不包含这些方法的。4.2 为什么官方要移除 hasScope因为scope本质就是 authority 的一种特殊形式。OAuth2 中JWT或 OAuth2 token里有个scope字段它代表客户端被授权的权限范围。在 Spring Security 内部这些 scope 会被转换成带SCOPE_前缀的SimpleGrantedAuthority对象。也就是说scope read在 Spring Security 内部就是authority SCOPE_readhasScope(read)等价于hasAuthority(SCOPE_read)官方认为没必要维护两套 API所以统一收敛到hasAuthority/hasAnyAuthority。这样权限模型就统一了不管这个权限来自角色、scope 还是自定义权限字段最终都是 authority。你不需要再区分这是 role 还是 scope来选不同的方法只需要知道它的前缀规则即可。4.3 新版本里的正规写法在新版本中正确的写法有两种。如果是配置类里针对路径的授权http.authorizeHttpRequests(auth - auth .requestMatchers(/api/invoices).hasAuthority(SCOPE_invoice.read) .anyRequest().authenticated() );如果是在方法注解上用PreAuthorize(hasAuthority(SCOPE_read)) public ListInvoice getInvoices() { // ... }注意SCOPE_前缀是必须的。很多人在迁移时只是机械地删除 hasScope 换成 hasAuthority忘记加前缀结果权限全部失效接口全部返回 403。4.4 在文档里精准定位 OAuth2 相关章节官网文档在Servlet Applications - OAuth2模块下分了几大块OAuth2 Client客户端模式用于接入第三方登录、获取 token。OAuth2 Resource Server资源服务器模式用于校验 JWT 或 opaque token。OAuth2 Authorization Server授权服务器Spring Security 不再直接支持需要迁移到 Spring Authorization Server 项目。你要根据项目角色选对应章节。资源服务器这块重点看 JWT 那一节里面讲了JwtDecoder、JwtAuthenticationConverter以及自定义 token 解析的方式。如果项目里有调用用户信息userinfo、刷新 token 等就属于 OAuth2 Client 章节的范畴。里面的OAuth2LoginAuthenticationFilter的过滤位置、OAuth2AuthorizedClientManager的配置方式都是实际开发中绕不开的细节。4.5 实际项目迁移时需要动的代码假设你正在把一个 Spring Boot 2.7 Spring Security 5.7 的项目升级到 Spring Boot 3.x Spring Security 6.x涉及 hasScope 的改动主要是这几处所有方法上的PreAuthorize(hasScope(read))改成PreAuthorize(hasAuthority(SCOPE_read))。所有 SecurityFilterChain 里的.hasScope(read)改成.hasAuthority(SCOPE_read)。如果你的自定义 AuthenticationConverter 之前没有添加 SCOPE_ 前缀升级后要检查JwtGrantedAuthoritiesConverter的默认行为。Spring Security 6.x 默认会把scope字段转换成SCOPE_xxx形式的 authority但如果你重写了 converter可能就丢了这个默认行为导致权限判断不一致。检查是否有其他地方依赖了旧版的OAuth2AccessToken相关 API这类 API 在 6.x 中的包名和类名有变化。实际测试下来最隐蔽的问题不在编译期而是运行时编译过了但权限全部 403。原因就是 scope 前缀没有被加进 authority。5. 从文档到实战我读 Spring Security 文档的几点心得5.1 文档里没有明确说清的隐藏规则过滤链的顺序就是安全策略的顺序。文档不会刻意提醒你但addFilterBefore/addFilterAfter的之前之后是相对 SecurityFilterChain 内部的顺序而言不是相对 Servlet 层级的顺序。如果你在 Servlet 容器里也加了过滤器两条链是平行的不会互相嵌套。任何一条 SecurityFilterChain 不生效先看 RequestMatcher。FilterChainProxy 是拿请求去匹配第一条匹配的链一旦匹配就固定使用这条链。所以如果前一条链的requestMatcher覆盖范围太广后面的链永远不会被触发这在多链配置里最容易出问题。授权表达式本质都是 authority 判断。hasRole、hasScope、hasAuthority底层都是对GrantedAuthority的匹配。文档会把它们分开写是为了让你读起来有语义区分。实战中我建议统一用hasAuthority前缀规则自己控制排查问题的时候思路最清晰。5.2 按需求驱动阅读不按章节顺序硬啃如果你要做的功能是给现有系统加一个手机号登录没必要从文档开头读起。我的做法是先把需求拆成几个关键词——手机号验证码 - 自定义认证流程 - AuthenticationProvider短信验证码校验 - 自定义逻辑登录成功后返回自定义 Token - 该走 JWT 还是 session。再凭这几个关键词直接在文档里搜对应章节。文档的全文搜索功能很好用直接搜AuthenticationProvider、UserDetailsService能快速定位到具体小节。读的时候只精读那几节的内容其余先跳过。这样读文档的效率比从头读到尾高得多。5.3 利用官方 Sample 和测试代码辅助理解只有文档还不够我还强烈建议配合官方 Sample 项目来学习。Spring Security 官方仓库spring-projects/spring-security的samples目录下有大量可运行的示例代码每一个示例对应文档里的某个具体场景。比如你想找JWT 资源服务器 method security 配合的完整示例直接去那里找。读示例代码的时候不要只盯着 SecurityFilterChain 的配置要把示例的测试代码也看了。Spring Security 官方对测试的支持spring-security-test很完善WithMockUser、SecurityMockMvcRequestPostProcessors这些工具在调试权限问题时能派上大用场。文档的 Test 章节对这一部分有完整介绍。5.4 版本升级时最实用的一个技巧Spring Security 5.8 官方给了一个迁移指南文档专门讲从 5.7 到 5.8 再到 6.0 的 API 变化。这篇文档在官网导航里叫Migration Guide是版本升级前的必读材料。里面列了一个已移除/已废弃 API 对照表按表逐项检查项目代码能省掉大量搜报错信息的时间。最后一个切身的体会Spring Security 的文档属于越看越薄的类型。第一次读觉得信息量巨大、名词满天飞但当你在项目里真的踩过几个坑再回去读同一段内容会突然发现作者把每种情况都写得很清楚只是当初没注意到。所以如果你现在觉得看不懂不用焦虑也别急着换博客你只是缺一些真实的实战经验。把文档留在手边遇到问题回头翻一翻几个项目下来自然就通了。

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

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

免费获取报价