当前位置: 首页 > news >正文

【Spring Security】安全过滤链

Spring Security

  • 安全过滤链(Security Filter Chain)
    • Security Filter Chain 概览
    • 默认核心过滤器职责
    • 自定义过滤器的添加与位置控制
    • FilterChainProxy

安全过滤链(Security Filter Chain)

Security Filter Chain 概览

Spring Security 的“执行载体”

Spring Security 的核心本质其实是一条 Servlet Filter 链
当请求进入 Spring Boot 应用时,Spring Security 会把自己的一组过滤器“挂”到 Servlet 的过滤器机制上。

换句话说:

Security 的认证与授权逻辑不是直接写在 Controller 里,而是由一连串 Filter 在请求进入 Controller 之前完成。

请求接入流程(Spring MVC 与 Security)

我们先看一个从浏览器到 Controller 的典型请求路径:

浏览器请求↓
Tomcat (Servlet 容器)↓
DispatcherServlet(Spring MVC)↓
HandlerMapping → Controller

但当你引入 Spring Security 后,路径会变成这样:

浏览器请求↓
Tomcat↓
DelegatingFilterProxy(代理 Filter)↓
FilterChainProxy(Security 的总调度器)↓
SecurityFilterChain(多条安全链之一)↓
15+ 内置过滤器依次执行(认证/授权/异常处理)↓
DispatcherServlet↓
Controller

所以说:Spring Security 并不是“接管”Controller,而是通过过滤器链包裹了整个请求过程。

DelegatingFilterProxy 与 FilterChainProxy 的关系

这两个名字很像,容易混淆,我们分清楚:

组件所属层职责
DelegatingFilterProxyServlet 容器层(Tomcat)是一个“桥梁”,把 Servlet 的过滤调用转交给 Spring 容器中的 Bean
FilterChainProxySpring Security 内部是真正的安全过滤器调度器,负责执行所有 SecurityFilterChain

执行顺序大概是:

Tomcat → DelegatingFilterProxy → FilterChainProxy → SecurityFilterChain → 各种Security Filter

重点:
DelegatingFilterProxy 是一个普通的 Servlet 过滤器,它会查找 Spring 容器中名为 "springSecurityFilterChain" 的 Bean,然后把过滤任务交给它。这个 Bean 就是 FilterChainProxy

SecurityFilterChain 的组成与匹配机制

一个应用可以有多个 SecurityFilterChain

每条链对应一组安全配置。

例如:

@Configuration
@EnableWebSecurity
public class MultiSecurityConfig {@BeanSecurityFilterChain apiChain(HttpSecurity http) throws Exception {http.securityMatcher("/api/**") // 只匹配 API 请求.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).httpBasic();return http.build();}@BeanSecurityFilterChain webChain(HttpSecurity http) throws Exception {http.securityMatcher("/web/**") // 只匹配 Web 页面.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()).formLogin();return http.build();}
}

当请求进入时:

  • 如果路径是 /api/** → 使用第一条链;
  • 如果路径是 /web/** → 使用第二条链。

匹配机制:
FilterChainProxy 会逐条检查 SecurityFilterChain,找到第一个 .matches(request)true 的链,然后只执行那一条。

请求从进入到放行的简化流程图

在这里插入图片描述

默认核心过滤器职责

Spring Security 的整个认证与授权机制,都是靠一条巨大的 “过滤链”(Security Filter Chain) 实现的。
所有的 HTTP 请求(如 /login, /user, /api/**)都会经过这些过滤器层层处理。

每个过滤器(Filter)负责一块安全逻辑:

  • 有的做登录认证;
  • 有的做登出;
  • 有的检查权限;
  • 有的防止 CSRF;
  • 有的加安全 Header;
  • 有的包装请求对象。

底层类: org.springframework.security.web.FilterChainProxy 它负责:

  • 根据 URL 匹配不同的过滤链;
  • 依次执行每个过滤器;
  • 控制异常处理与认证授权的流程。

Spring Security 默认过滤器链(15+ 核心过滤器)

  1. WebAsyncManagerIntegrationFilter

    • WebAsyncManagerIntegrationFilter 是 Spring Security 过滤器链中的第一个核心过滤器,主要解决 异步请求处理中的安全上下文传递问题,确保异步线程(如 @Async 方法、异步控制器)能正确获取当前用户的认证信息。

    • 作用是 将请求线程的 SecurityContext 与 Spring Web 的异步管理器(WebAsyncManager)绑定,确保异步线程执行时能继承原始请求的安全上下文。

    • 具体工作流程:

      1. 请求到达时:过滤器拦截请求,从 SecurityContextHolder 中获取当前线程的 SecurityContext(包含用户认证信息)。

      2. 绑定到异步管理器:将 SecurityContext 存入 WebAsyncManager(Spring 管理异步请求的核心组件)的属性中,确保异步任务启动时能拿到这个上下文。

      3. 异步线程执行时:当异步任务(如 @Async 方法)开始执行前,WebAsyncManager 会将绑定的 SecurityContext 重新设置到异步线程的 SecurityContextHolder 中。

      4. 异步任务结束后:自动清除异步线程的 SecurityContextHolder,避免线程池中的线程被复用后导致安全上下文泄露。

    • 代码示例:假设一个异步控制器方法

      @RestController
      public class AsyncController {@GetMapping("/async")public CompletableFuture<String> asyncRequest() {// 此处为请求线程,能正常获取 SecurityContextAuthentication auth = SecurityContextHolder.getContext().getAuthentication();String username = auth.getName();// 异步处理(由其他线程执行)return CompletableFuture.supplyAsync(() -> {// 若没有 WebAsyncManagerIntegrationFilter,此处 auth 会为 nullAuthentication asyncAuth = SecurityContextHolder.getContext().getAuthentication();return "异步线程获取的用户名:" + asyncAuth.getName();});}
      }
      
      • WebAsyncManagerIntegrationFilter:异步线程中 asyncAuth 能正确获取用户名。

      • 无此过滤器:异步线程中 asyncAuthnull(安全上下文丢失)。

  2. SecurityContextHolderFilter

    • SecurityContextHolderFilter 是 Spring Security 6.x 及以上版本新增的核心过滤器(替代了旧版的 SecurityContextPersistenceFilter),负责 管理 SecurityContext 的生命周期,是整个安全上下文(用户认证信息)在请求过程中传递的 “核心管家”。

    • 作用是 在请求开始时加载 SecurityContextSecurityContextHolder,在请求结束时保存 SecurityContext 并清理资源,确保用户认证状态能跨请求共享(如登录后,后续请求无需重复认证)。

    • 工作流程(分两步)

      1. 请求到达时:加载 SecurityContext 到线程

        当一个请求进入应用时,过滤器会:

        • Session 中获取之前保存的 SecurityContext(如果存在,比如用户已登录);
        • 若 Session 中没有 SecurityContext(如首次访问或未登录用户),则创建一个空的 SecurityContext
        • 将获取到的 SecurityContext 设置到 SecurityContextHolder 中(绑定到当前请求线程的 ThreadLocal 变量)。

        此时,后续的过滤器(如认证、授权过滤器)和控制器(Controller)就能通过 SecurityContextHolder.getContext() 轻松获取用户认证信息。

      2. 请求结束时:保存并清理 SecurityContext

        当请求处理完成(响应返回给客户端)时,过滤器会:

        • SecurityContextHolder 中取出当前线程的 SecurityContext
        • SecurityContext 保存回 Session(确保下次请求能复用认证状态);
        • 清除 SecurityContextHolder 中的 SecurityContext(避免线程池复用线程时导致的信息泄露)。
    • 代码示例:假设用户已登录,在控制器中获取用户信息

      @RestController
      public class UserController {@GetMapping("/user")public String getCurrentUser() {// 直接从 SecurityContextHolder 获取认证信息// 这些信息正是由 SecurityContextHolderFilter 从 Session 加载到线程中的Authentication auth = SecurityContextHolder.getContext().getAuthentication();return "当前登录用户:" + auth.getName();}
      }
      
      • 如果没有 SecurityContextHolderFilterSecurityContextHolder.getContext() 会返回空,无法获取用户信息;

      • 有此过滤器:即使是跨请求(如登录后访问多个接口),SecurityContext 也能通过 Session 保存,实现认证状态的延续。

  3. HeaderWriterFilter

    • HeaderWriterFilter 是 Spring Security 过滤器链中的第三个核心过滤器,主要职责是 自动向 HTTP 响应头中添加安全相关的头部信息,通过浏览器的内置安全机制增强应用的安全性,属于 “防御性安全措施”。

    • HeaderWriterFilter 通过内部的 HeaderWriter 接口实现响应头的添加,默认情况下会添加以下关键安全头(可通过配置自定义或禁用):

      响应头作用说明
      X-Content-Type-Options: nosniff禁止浏览器对响应内容进行 “MIME 类型嗅探”。例如:防止将本应作为文本的 .txt 文件被浏览器误认为可执行脚本(text/plaintext/html),减少 XSS 风险。
      X-Frame-Options: DENY禁止当前页面被嵌入到任何 <iframe><frame>中,防御 “点击劫持” 攻击(攻击者诱导用户点击嵌入在恶意页面中的合法按钮)。可选值:DENY(完全禁止)、SAMEORIGIN(仅允许同域嵌入)。
      X-XSS-Protection: 1; mode=block启用浏览器内置的 XSS 过滤器。- 1 表示启用;- mode=block 表示检测到 XSS 攻击时,直接阻止页面加载(而非尝试修复),避免部分攻击绕过过滤。
      Strict-Transport-Security: max-age=31536000; includeSubDomains(可选)启用 HTTP 严格传输安全(HSTS),强制浏览器后续仅通过 HTTPS 访问本域名及子域名,防止 “中间人攻击” 降级到 HTTP。- max-age:HSTS 规则在浏览器中的缓存时间(秒);- 需通过配置显式启用(默认不添加)。
    • 工作流程

      1. 请求处理阶段:当请求经过 HeaderWriterFilter 时,它会暂存当前的响应对象(HttpServletResponse)。

      2. 响应返回前:在响应即将发送给客户端时,HeaderWriterFilter 调用内部的 HeaderWriter 实现类,向响应头中添加预设的安全头。

      3. 自定义扩展:开发者可通过配置添加自定义头(如 Content-Security-Policy)或修改默认头的取值。

    • 代码示例:自定义或修改响应头

      在 Spring Security 配置类中,可通过 headers() 方法定制 HeaderWriterFilter 的行为

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http// 配置响应头.headers(headers -> headers// 修改 X-Frame-Options 为 SAMEORIGIN(允许同域嵌入).frameOptions(frame -> frame.sameOrigin())// 启用 HSTS 头.httpStrictTransportSecurity(hsts -> hsts.includeSubDomains(true).maxAgeInSeconds(31536000) // 1年)// 添加自定义头(如内容安全策略).addHeaderWriter(new StaticHeadersWriter("Content-Security-Policy", "default-src 'self'; script-src 'self'"))// 禁用某个默认头(如 X-XSS-Protection).xssProtection(xss -> xss.disable()));return http.build();}
      }
      
  4. CorsFilter

    • CorsFilter 是 Spring Security 中处理跨域资源共享(CORS) 的过滤器,用于解决前端应用(如 Vue、React)与后端 API 不在同一域名下时的跨域请求限制问题。它并非默认启用,需通过配置显式开启,是前后端分离项目中的常用组件。

    • CorsFilter 会拦截所有请求,根据预设的跨域规则(如允许的来源、方法、请求头等)进行校验,并在响应中添加对应的 CORS 头,具体工作流程如下:

      1. 拦截请求,提取跨域信息:从请求中解析跨域相关参数,如:

        • Origin 头:表示请求来自哪个域名(如 http://localhost:3000);
        • Access-Control-Request-Method 头:表示前端请求使用的 HTTP 方法(如 POST、PUT);
        • Access-Control-Request-Headers 头:表示前端请求携带的自定义头(如 Authorization)。
      2. 根据配置的跨域规则进行校验:检查请求的来源、方法、头等是否符合后端配置的允许规则(如是否在允许的域名列表中)。

      3. 生成 CORS 响应头:若校验通过,向响应中添加允许跨域的头信息,常见的有:

        响应头作用说明
        Access-Control-Allow-Origin指定允许跨域请求的来源(如 http://localhost:3000* 表示允许所有)。
        Access-Control-Allow-Methods指定允许跨域请求的 HTTP 方法(如 GET, POST, PUT, DELETE)。
        Access-Control-Allow-Headers指定允许跨域请求携带的自定义头(如 Authorization, Content-Type)。
        Access-Control-Allow-Credentials表示是否允许跨域请求携带 Cookie(truefalse),配合前端 withCredentials 使用。
        Access-Control-Max-Age指定预检请求(OPTIONS)的结果缓存时间(秒),避免频繁校验。
      4. 处理预检请求(OPTIONS):对于 “非简单请求”(如 POST 且 Content-Type 为 application/json、使用自定义头、PUT/DELETE 方法等),浏览器会先发送一次 OPTIONS 预检请求,验证后端是否允许跨域。CorsFilter 会处理预检请求,返回上述 CORS 头,无需业务逻辑介入。

    • 代码示例:启用与配置

      在 Spring Security 中,需通过 .cors() 显式启用 CorsFilter,并配置跨域规则:

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http// 启用 CORS 过滤器.cors(cors -> cors// 配置跨域规则.configurationSource(corsConfigurationSource()))// 其他配置(如关闭 CSRF,前后端分离常用).csrf(csrf -> csrf.disable());return http.build();}// 定义跨域规则@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration config = new CorsConfiguration();// 允许的来源(生产环境需指定具体域名,避免 * 带来的安全风险)config.setAllowedOrigins(Arrays.asList("http://localhost:3000"));// 允许的方法config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));// 允许的请求头(包括自定义头,如 Authorization)config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));// 允许携带 Cookie(如需)config.setAllowCredentials(true);// 预检请求缓存时间(3600秒 = 1小时)config.setMaxAge(3600L);// 应用到所有路径UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", config);return source;}
      }
      
  5. CsrfFilter

    • CsrfFilter 是 Spring Security 中用于防御跨站请求伪造(CSRF)攻击的核心过滤器,默认启用,主要针对 “修改型请求”(如 POST、PUT、DELETE 等)进行安全校验,是 Web 应用常见的安全防护手段。

    • CsrfFilter 通过令牌校验机制防止 CSRF 攻击,确保请求是用户 “主动且知情” 发起的,而非恶意网站伪造的。其核心逻辑是:

      1. 为每个用户会话生成一个唯一的 CSRF 令牌(随机字符串),存储在 Session 中。
      2. 要求所有 “修改型请求”(非 GET、HEAD、OPTIONS、TRACE)必须携带此令牌。
      3. 校验请求中的令牌与 Session 中存储的令牌是否一致,不一致则拒绝请求。
    • 工作流程

      1. 生成与存储令牌
        • 用户首次访问应用时,CsrfFilter 会生成一个 CSRF 令牌(如 743f3a3b-2e11-4d4b-9f2d-21655b9f2d1a),存储在 Session 中(HttpSessionCsrfTokenRepository 是默认实现)。
        • 令牌会通过 请求属性_csrf)暴露给前端(如用于表单隐藏域或请求头)。
      2. 前端携带令牌:前端在发起 “修改型请求” 时,必须通过以下方式之一携带令牌:
        • 表单提交:通过隐藏域 <input type="hidden" name="_csrf" value="${_csrf.token}"/>
        • AJAX 请求:通过请求头(如 X-CSRF-TOKEN)携带令牌值。
      3. 后端校验令牌
        • CsrfFilter 拦截请求,提取请求中的令牌(从参数 _csrf 或头 X-CSRF-TOKEN 中获取)。
        • 对比请求令牌与 Session 中存储的令牌:
          • 一致:放行请求,继续处理。
          • 不一致或缺失:抛出 CsrfException,拒绝请求(默认返回 403 Forbidden)。
    • 代码示例:自定义 CSRF 行为

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf// 自定义令牌存储库(默认是 HttpSessionCsrfTokenRepository).csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())// 忽略某些路径的 CSRF 校验(如第三方回调、API 接口).ignoringRequestMatchers("/api/webhook/**", "/public/**")// 自定义 CSRF 异常处理器(返回 JSON 而非默认页面).accessDeniedHandler((request, response, ex) -> {response.setContentType("application/json;charset=UTF-8");response.getWriter().write("{\"error\":\"CSRF 令牌验证失败\"}");}));return http.build();}
      }
      
      • CookieCsrfTokenRepository:将令牌存储在 Cookie 中(而非 Session),适合前后端分离场景(无 Session 时仍可传递令牌)。
      • ignoringRequestMatchers:对无需 CSRF 保护的接口(如公开 API、第三方回调)关闭校验,减少不必要的开销。
    • 何时需要禁用 CSRF?

      CsrfFilter 默认启用,但以下场景通常会禁用:

      1. 前后端分离 + JWT 无状态认证:JWT 令牌通常通过请求头(如 Authorization)传递,且无 Session,CSRF 攻击风险极低(攻击方无法获取 JWT 令牌)。
      2. 纯 API 服务:仅对外提供 API 且客户端不是浏览器(如移动端 App),不存在 CSRF 攻击的前提(浏览器自动携带 Cookie)。

      禁用方式:

      http.csrf(csrf -> csrf.disable());
      
  6. LogoutFilter

    • LogoutFilter 是 Spring Security 中专门处理用户登出流程的核心过滤器,负责拦截登出请求并执行一系列清理操作,确保用户安全退出系统。它在过滤器链中位于 CSRF 过滤器之后,认证相关过滤器之前,是登出功能的 “总调度器”。

    • 用户发起登出请求时,LogoutFilter 会按固定逻辑完成以下操作,确保用户认证状态被彻底清除:

      1. 清除安全上下文:删除 SecurityContextHolder 中存储的当前用户认证信息(Authentication),使当前线程无法再获取用户身份。
      2. 使会话失效:调用 HttpSession.invalidate() 销毁当前用户会话,防止会话被复用。
      3. 删除关联 Cookie:移除与认证相关的 Cookie(如 JSESSIONID、“记住我” 功能的 remember-me Cookie)。
      4. 清除 “记住我” 令牌:如果启用了 “记住我” 功能,会通知 RememberMeServices 失效对应的令牌,避免用户再次通过 “记住我” 自动登录。
      5. 触发登出成功处理器:登出完成后,调用 LogoutSuccessHandler 处理后续逻辑(如重定向到登录页、返回登出成功的 JSON 响应)。
    • 工作流程

      1. 拦截登出请求LogoutFilter 会拦截预设的登出路径(默认是 GET /logout,可通过配置修改路径和请求方法)。例如:用户点击 “退出登录” 按钮,前端发送 POST /api/logout 请求,LogoutFilter会捕获该请求。
      2. 执行登出操作:调用配置的 LogoutHandler 列表(默认包含以下处理器,可自定义扩展):
        • SecurityContextLogoutHandler:负责清除 SecurityContext 和使会话失效(核心处理器)。
        • CookieClearingLogoutHandler:删除指定的 Cookie(如配置了 .deleteCookies("JSESSIONID"))。
        • RememberMeServices(若启用):清除 “记住我” 令牌。
      3. 处理登出结果:登出操作完成后,由 LogoutSuccessHandler 决定后续行为:
        • 传统 Web 应用:默认重定向到登录页(附加 ?logout 参数,如 /login?logout)。
        • 前后端分离应用:通常返回 JSON 响应(如 {"success": true, "message": "登出成功"}),由前端处理跳转。
    • 代码示例:自定义登出行为

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.logout(logout -> logout// 自定义登出请求路径(默认是 /logout).logoutUrl("/api/logout")// 允许的请求方法(默认只支持 GET,前后端分离常用 POST).logoutMethod(HttpMethod.POST)// 登出时删除的 Cookie.deleteCookies("JSESSIONID", "remember-me")// 清除“记住我”令牌(需注入 RememberMeServices).rememberMeServices(rememberMeServices())// 登出成功后不保存请求缓存(避免重定向到登出前的页面).clearAuthentication(true).invalidateHttpSession(true)// 自定义登出成功处理器(前后端分离返回 JSON).logoutSuccessHandler((request, response, authentication) -> {response.setContentType("application/json;charset=UTF-8");response.getWriter().write("{\"success\":true, \"message\":\"登出成功\", \"redirect\":\"/login\"}");})// 登出失败处理器(如令牌无效时).logoutFailureHandler((request, response, exception) -> {response.setStatus(HttpServletResponse.SC_BAD_REQUEST);response.getWriter().write("{\"error\":\"登出失败:" + exception.getMessage() + "\"}");}));return http.build();}// 假设启用了“记住我”功能,需配置对应的 RememberMeServices@Beanpublic RememberMeServices rememberMeServices() {return new PersistentTokenBasedRememberMeServices("remember-me-key",userDetailsService(),persistentTokenRepository());}// 其他依赖 Bean(省略实现)@Bean public UserDetailsService userDetailsService() { ... }@Bean public PersistentTokenRepository persistentTokenRepository() { ... }
      }
      
  7. OAuth2AuthorizationRequestRedirectFilter

    • OAuth2AuthorizationRequestRedirectFilter 是 Spring Security 中处理 OAuth2 认证流程第一步(授权请求重定向) 的过滤器,仅在集成第三方登录(如 GitHub、Google、微信登录等)时启用。它的核心作用是:拦截前端发起的第三方登录请求,生成符合 OAuth2 协议的授权 URL,并将用户重定向到第三方授权服务器的登录授权页面

    • 核心职责

      1. 拦截第三方登录请求:监听预设的第三方登录触发路径(默认格式为 /oauth2/authorization/{registrationId},其中 registrationId 是第三方服务的唯一标识,如 githubwechat)。例如:用户点击 “使用 GitHub 登录”,前端跳转至 /oauth2/authorization/github,此请求会被该过滤器拦截。
      2. 构建 OAuth2 授权请求:根据预配置的第三方服务信息(如客户端 ID、客户端密钥、授权范围、回调地址等),生成符合 OAuth2 协议的授权请求参数,包括:
        • response_type=code(授权码模式,最常用的 OAuth2 模式);
        • client_id(你的应用在第三方平台的唯一标识);
        • redirect_uri(授权成功后第三方服务器回调你的应用的地址);
        • scope(请求的权限范围,如 user:email 表示获取用户邮箱);
        • state(随机字符串,用于防止 CSRF 攻击,后续会验证)。
      3. 重定向到第三方授权页面:将上述参数拼接为第三方授权服务器的授权 URL(如 GitHub 的 https://github.com/login/oauth/authorize?client_id=xxx&redirect_uri=xxx&...),并将用户浏览器重定向到该 URL,引导用户在第三方平台完成登录和授权。
    • 工作流程(以 GitHub 登录为例)

      1. 用户触发第三方登录:前端页面点击 “GitHub 登录”,请求后端路径 /oauth2/authorization/github
      2. 过滤器拦截并生成授权请求
        • OAuth2AuthorizationRequestRedirectFilter 识别到 registrationId=github,从配置中读取 GitHub 第三方服务的信息(客户端 ID、回调地址等)。
        • 生成随机 state 参数,存储到 Session 或 OAuth2AuthorizationRequestRepository 中(用于后续回调验证)。
        • 拼接授权 URL:https://github.com/login/oauth/authorize?client_id=你的GitHub客户端ID&redirect_uri=https://你的应用域名/login/oauth2/code/github&response_type=code&scope=user:email&state=随机字符串
      3. 重定向到 GitHub 授权页:用户浏览器被重定向到上述 URL,显示 GitHub 的登录页面(若用户已登录 GitHub,则直接显示授权确认页,询问是否允许你的应用获取其邮箱等信息)。
      4. 后续流程:用户授权后,GitHub 会将用户重定向回你的应用的回调地址(如 /login/oauth2/code/github),并携带 code(授权码)和 state 参数,后续由 OAuth2LoginAuthenticationFilter 处理(验证 state、用 code 换令牌等)。
    • 代码示例:启用 GitHub 登录

      要启用 OAuth2AuthorizationRequestRedirectFilter,需引入 OAuth2 客户端依赖并配置第三方服务信息:

      1. 引入依赖(Maven)

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        
      2. 配置第三方服务信息(application.yml)

        spring:security:oauth2:client:registration:# registrationId 为 githubgithub:client-id: 你的GitHub客户端ID  # 在GitHub开发者平台申请client-secret: 你的GitHub客户端密钥scope: user:email  # 请求的权限范围provider:github:authorization-uri: https://github.com/login/oauth/authorize  # 授权页URL(GitHub固定)token-uri: https://github.com/login/oauth/access_token  # 令牌获取URL(GitHub固定)user-info-uri: https://api.github.com/user  # 用户信息URL(GitHub固定)
        
      3. Spring Security 配置类

        @Configuration
        @EnableWebSecurity
        public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())// 启用 OAuth2 登录,自动注册 OAuth2AuthorizationRequestRedirectFilter 等过滤器.oauth2Login(oauth2 -> oauth2// 自定义登录页(可选,默认会生成一个登录页,包含第三方登录按钮).loginPage("/custom-login")// 自定义回调地址(需与第三方平台配置的回调地址一致,默认是 /login/oauth2/code/{registrationId}).redirectUri("/login/oauth2/code/github"));return http.build();}
        }
        
    • 注意事项

      1. registrationId 的作用:用于区分不同的第三方服务(如同时集成 GitHub 和 Google 登录时,registrationId 分别为 githubgoogle),过滤器通过它定位对应的第三方配置。
      2. state 参数的安全性:过滤器生成的 state 会存储在 Session 中,后续回调时会验证请求中的 state 与存储的是否一致,防止 CSRF 攻击和请求伪造。
      3. 回调地址的一致性:配置的 redirect_uri 必须与在第三方平台(如 GitHub 开发者中心)注册的回调地址完全一致,否则授权服务器会拒绝回调。
      4. 仅支持授权码模式:该过滤器主要用于 OAuth2 的 “授权码模式”(最安全的模式),不支持简化模式等其他模式。
  8. OAuth2LoginAuthenticationFilter

    • OAuth2LoginAuthenticationFilter 是 Spring Security 中处理 OAuth2 第三方登录流程第二步(授权回调与认证) 的核心过滤器,专门用于接收第三方授权服务器的回调请求,完成授权码验证、令牌获取和用户身份认证,最终生成用户在当前应用中的认证信息。

    • 核心职责

      1. 拦截第三方授权回调请求:监听预设的回调路径(默认格式为 /login/oauth2/code/{registrationId},其中 registrationId 是第三方服务标识,如 github)。例如:用户在 GitHub 授权后,被重定向到 https://你的应用域名/login/oauth2/code/github?code=xxx&state=xxx,此请求会被该过滤器拦截。
      2. 验证回调参数的合法性
        • 校验 state 参数:确保回调请求中的 state 与之前发送授权请求时存储的 state一致(防止 CSRF 攻击和请求伪造)。
        • 提取 code 参数:获取第三方服务器返回的授权码(后续用于换取令牌)。
      3. 用授权码换取访问令牌:向第三方授权服务器的令牌端点(如 GitHub 的 https://github.com/login/oauth/access_token)发送请求,携带授权码、客户端 ID、客户端密钥等参数,换取访问令牌(access_token)、刷新令牌(refresh_token)等信息。
      4. 获取第三方用户信息:使用获取到的访问令牌,调用第三方服务器的用户信息端点(如 GitHub 的 https://api.github.com/user),获取用户的基本信息(用户名、邮箱、头像等)。
      5. 创建本地认证信息:将第三方用户信息转换为 Spring Security 可识别的 Authentication 对象(OAuth2AuthenticationToken),包含用户身份、权限等信息,并将其存入 SecurityContextHolder,标记用户在当前应用中已登录。
      6. 处理认证结果
        • 认证成功:触发 AuthenticationSuccessHandler(默认重定向到应用首页,可自定义为返回 JSON 或跳转到指定页面)。
        • 认证失败:触发 AuthenticationFailureHandler(默认返回错误页面,可自定义为返回错误信息)。
    • 工作流程(以 GitHub 登录为例,承接上一步的授权重定向)

      1. 第三方授权回调:用户在 GitHub 授权后,被重定向到 https://你的应用域名/login/oauth2/code/github?code=abc123&state=xyz456

      2. 过滤器拦截并验证参数

        • OAuth2LoginAuthenticationFilter 拦截请求,提取 code=abc123state=xyz456
        • 验证 state:对比请求中的 xyz456 与之前存储在 Session 中的 state(由 OAuth2AuthorizationRequestRedirectFilter 生成),一致则继续。
      3. 换取访问令牌:过滤器向 GitHub 的令牌端点发送请求:

        POST https://github.com/login/oauth/access_token
        参数:code=abc123client_id=你的GitHub客户端IDclient_secret=你的GitHub客户端密钥redirect_uri=https://你的应用域名/login/oauth2/code/github
        

        GitHub 返回令牌信息:access_token=gho_xxx&token_type=bearer

      4. 获取用户信息:过滤器使用 access_token=gho_xxx 调用 GitHub 用户信息接口:

        GET https://api.github.com/user
        Header: Authorization: bearer gho_xxx
        

        GitHub 返回用户信息(如 login=zhangsanemail=zhangsan@github.com)。

      5. 创建本地认证对象:将 GitHub 用户信息转换为 OAuth2AuthenticationToken,包含用户名 zhangsan、权限 ROLE_USER 等,并设置到 SecurityContextHolder 中,用户在当前应用中登录成功。

      6. 跳转至首页:通过 AuthenticationSuccessHandler 重定向到应用首页(如 /home)。

    • 代码示例:自定义 OAuth2 登录回调行为

      在 Spring Security 配置中,可通过 oauth2Login() 定制 OAuth2LoginAuthenticationFilter 的行为:

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).oauth2Login(oauth2 -> oauth2// 自定义回调路径(需与第三方平台配置一致).redirectUri("/oauth2/callback/{registrationId}")// 自定义认证成功处理器(如返回JSON或跳转到指定页面).successHandler((request, response, authentication) -> {// authentication 为 OAuth2AuthenticationToken,包含第三方用户信息String username = authentication.getName();response.sendRedirect("/welcome?user=" + username);})// 自定义认证失败处理器.failureHandler((request, response, exception) -> {response.setContentType("application/json;charset=UTF-8");response.getWriter().write("{\"error\":\"第三方登录失败:" + exception.getMessage() + "\"}");})// 自定义用户信息映射(将第三方用户信息转换为本地UserDetails).userInfoEndpoint(userInfo -> userInfo.userService(oauth2UserService())));return http.build();}// 自定义用户信息服务:将第三方用户信息转换为本地UserDetailsprivate OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {return new DefaultOAuth2UserService() {@Overridepublic OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {OAuth2User oauth2User = super.loadUser(userRequest);// 从第三方用户信息中提取字段(如GitHub的"login"对应用户名,"email"对应邮箱)Map<String, Object> attributes = oauth2User.getAttributes();String username = (String) attributes.get("login");String email = (String) attributes.get("email");// 构建本地用户信息(可添加自定义权限)return User.withUsername(username).password("N/A") // 第三方登录无需本地密码.authorities("ROLE_USER").build();}};}
      }
      
    • 注意事项

      1. 回调路径的一致性:配置的回调路径(如 /login/oauth2/code/github)必须与在第三方平台(如 GitHub 开发者中心)注册的回调地址完全一致,否则第三方服务器会拒绝回调。
      2. 用户信息的映射:第三方返回的用户信息字段(如 GitHub 的 login、微信的 nickname)可能不同,需通过 userInfoEndpoint().userService() 自定义映射逻辑,将其转换为本地应用的 UserDetailsOAuth2User 对象。
      3. 令牌的存储与刷新:过滤器默认会将访问令牌、刷新令牌等信息存储在 OAuth2AuthorizedClientService中,可用于后续访问第三方 API(如调用 GitHub 接口获取用户仓库)。若令牌过期,会自动使用刷新令牌获取新令牌(需第三方支持)。
      4. 仅用于授权码模式:该过滤器仅支持 OAuth2 的 “授权码模式”(最安全的模式),与 OAuth2AuthorizationRequestRedirectFilter 配合完成完整的第三方登录流程。
  9. UsernamePasswordAuthenticationFilter

    • UsernamePasswordAuthenticationFilter 是 Spring Security 中处理 基于用户名 / 密码的表单登录 的核心过滤器,默认拦截 POST /login 请求,负责从请求中提取用户名和密码,执行认证逻辑,并处理认证成功 / 失败的后续操作。它是传统账号密码登录场景的 “核心处理器”。

    • 核心职责

      1. 拦截登录请求:专门拦截预设的登录路径(默认是 POST /login,可通过配置修改路径和请求方法),仅处理携带用户名和密码的登录请求。
      2. 提取认证凭证:从请求参数中提取 usernamepassword(默认参数名,可自定义),构建未认证的 UsernamePasswordAuthenticationToken 对象(包含用户名、密码,但未设置认证状态)。
      3. 执行认证逻辑:将未认证的 UsernamePasswordAuthenticationToken 交给 AuthenticationManager(认证管理器)处理,由其调用 UserDetailsService 加载用户信息,并通过 PasswordEncoder 校验密码是否匹配。
      4. 处理认证结果
        • 认证成功:触发 AuthenticationSuccessHandler(默认重定向到首页,可自定义为返回 JSON 令牌等),并将认证通过的 Authentication 对象存入 SecurityContextHolder
        • 认证失败:触发 AuthenticationFailureHandler(默认返回错误页面,可自定义为返回错误信息)。
    • 工作流程(传统表单登录为例)

      1. 用户提交登录表单:前端通过表单(method="POST"action="/login")提交用户名和密码,请求格式为 application/x-www-form-urlencoded(如 username=admin&password=123456)。
      2. 过滤器拦截并提取凭证UsernamePasswordAuthenticationFilter 拦截 POST /login 请求,从请求参数中获取 username=adminpassword=123456,构建 UsernamePasswordAuthenticationToken(未认证状态)。
      3. 调用认证管理器:过滤器将令牌交给 AuthenticationManager,后者委托 UserDetailsService 加载用户信息(如从数据库查询 admin 的加密密码 $2a$10$xxx),再通过 PasswordEncoder 比对提交的密码与存储的加密密码是否一致。
      4. 处理认证结果
        • 成功:AuthenticationManager 返回认证通过的 Authentication 令牌(包含用户权限等信息),过滤器调用 AuthenticationSuccessHandler 重定向到 /home,并将令牌存入 SecurityContextHolder(标记用户已登录)。
        • 失败:AuthenticationManager 抛出异常(如 BadCredentialsException),过滤器调用 AuthenticationFailureHandler 重定向到 /login?error,显示错误信息。
    • 代码示例:自定义登录行为

      可通过 Spring Security 配置定制过滤器的行为,适应不同场景(如修改登录路径、自定义参数名、返回 JSON 等):

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/login").permitAll() // 登录页允许匿名访问.anyRequest().authenticated()).formLogin(form -> form// 自定义登录请求路径(默认是 /login).loginProcessingUrl("/api/login")// 自定义用户名/密码参数名(默认是 username/password).usernameParameter("user").passwordParameter("pass")// 认证成功处理器(前后端分离返回 JSON 令牌).successHandler((request, response, authentication) -> {response.setContentType("application/json;charset=UTF-8");// 生成 JWT 令牌(示例)String token = generateJwt(authentication);response.getWriter().write("{\"success\":true, \"token\":\"" + token + "\"}");})// 认证失败处理器.failureHandler((request, response, exception) -> {response.setContentType("application/json;charset=UTF-8");response.getWriter().write("{\"error\":\"登录失败:" + exception.getMessage() + "\"}");}));return http.build();}// 示例:生成 JWT 令牌(实际需引入 JWT 库实现)private String generateJwt(Authentication authentication) {return "jwt-token-for-" + authentication.getName();}// 配置密码编码器(必须配置,否则启动报错)@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 配置用户详情服务(从数据库加载用户,示例为内存用户)@Beanpublic UserDetailsService userDetailsService() {UserDetails user = User.withUsername("admin").password(passwordEncoder().encode("123456")).roles("ADMIN").build();return new InMemoryUserDetailsManager(user);}
      }
      

      前后端分离场景的适配问题与解决

      默认的 UsernamePasswordAuthenticationFilter 仅支持 application/x-www-form-urlencoded 格式(表单提交),而前后端分离项目通常使用 application/json 格式传递用户名和密码,此时需要 自定义过滤器替换它,示例如下:

      // 自定义 JSON 登录过滤器
      public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {// 从 JSON 中提取用户名和密码try {Map<String, String> credentials = new ObjectMapper().readValue(request.getInputStream(), Map.class);String username = credentials.get("username");String password = credentials.get("password");// 构建未认证令牌,调用认证管理器UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);return this.getAuthenticationManager().authenticate(token);} catch (IOException e) {throw new AuthenticationServiceException("解析登录参数失败", e);}}
      }// 在配置中替换默认过滤器
      @Configuration
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.addFilterAt(new JsonUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)// 其他配置...}
      }
      
    • 注意事项

      1. 请求格式限制:默认仅支持 application/x-www-form-urlencoded(表单),JSON 格式需自定义过滤器(如上例)。
      2. 登录路径与方法:默认拦截 POST /login,可通过 loginProcessingUrl() 修改路径,但必须使用 POST 方法(GET 方法不安全,会暴露密码)。
      3. 与 CSRF 的配合:若未禁用 CSRF,登录请求需携带 CSRF 令牌(如表单隐藏域或请求头),否则会被 CsrfFilter 拦截。
      4. 密码加密:必须配置 PasswordEncoder(如 BCryptPasswordEncoder),否则认证时会抛出 IllegalArgumentException(Spring Security 5+ 强制要求)。
  10. DefaultLoginPageGeneratingFilter

    • DefaultLoginPageGeneratingFilter 是 Spring Security 中一个辅助性过滤器,主要作用是:当开发者未自定义自定义自定义登录页面时,自动生成一个默认的 HTML 登录表单页面,避免因缺少登录页导致的 404 错误或认证流程中断。

    • 核心职责

      1. 检测是否存在自定义登录页:过滤器会判断开发者是否通过 .formLogin().loginPage("/custom-login") 配置了自定义登录页。如果已配置,此过滤器不生效;如果未配置,它会自动介入。
      2. 生成默认登录表单页面:当用户访问需要认证的资源(如 /home)时,若未登录,会被重定向到默认登录路径(默认 /login)。此时,该过滤器会生成一个包含以下元素的 HTML 登录页面:
        • 用户名输入框(name="username");
        • 密码输入框(name="password");
        • 提交按钮(type="submit");
        • CSRF 令牌隐藏域(name="_csrf",用于防御 CSRF 攻击);
        • “记住我” 选项(若启用 rememberMe() 配置);
        • 错误提示区域(当登录失败时显示错误信息,如 “用户名或密码错误”)。
      3. 处理登录页请求:拦截对默认登录路径(/login)的 GET 请求,返回生成的登录页面 HTML;对于 POST 请求(实际提交登录表单),则放行给 UsernamePasswordAuthenticationFilter 处理。
    • 工作流程

      1. 用户访问受保护资源:例如用户未登录时访问 /dashboard,Spring Security 会判断用户未认证,将其重定向到默认登录页路径 /login
      2. 过滤器拦截登录页请求DefaultLoginPageGeneratingFilter 拦截 GET /login 请求,检测到未配置自定义登录页,开始生成默认登录页面。
      3. 生成并返回登录页面:动态生成包含用户名、密码输入框和 CSRF 令牌的 HTML 页面,响应给浏览器,用户看到默认登录表单。
      4. 用户提交登录表单:用户输入账号密码后提交,表单以 POST /login 发送请求,此请求会被 UsernamePasswordAuthenticationFilter 拦截处理(DefaultLoginPageGeneratingFilter 不处理 POST 请求)。
      5. 登录失败场景:若认证失败(如密码错误),用户会被重定向回 /login?error,过滤器会在页面中显示错误信息(从请求参数中读取错误原因)。
    • 默认登录页面的特点

      • 极简风格:页面为纯 HTML 样式,无复杂 CSS,仅包含核心登录元素,适合快速测试或演示。
      • 自动适配配置
        • 若启用了 “记住我” 功能(.rememberMe()),页面会自动添加 “记住我” 复选框(name="remember-me")。
        • 若配置了 OAuth2 第三方登录,页面会自动添加第三方登录按钮(如 “使用 GitHub 登录”)。
      • CSRF 保护:自动包含 CSRF 令牌隐藏域,避免登录请求被 CSRF 攻击。
    • 如何禁用默认登录页?

      在实际项目中(尤其是生产环境),通常需要使用自定义登录页,此时可通过以下方式禁用 DefaultLoginPageGeneratingFilter

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).formLogin(form -> form// 配置自定义登录页路径(此时 DefaultLoginPageGeneratingFilter 自动失效).loginPage("/custom-login")// 允许匿名访问自定义登录页.permitAll());return http.build();}
      }
      
      • 配置 loginPage("/custom-login") 后,Spring Security 会认为开发者已提供自定义登录页,DefaultLoginPageGeneratingFilter 不再生效。
      • 需确保 /custom-login 路径对应一个有效的控制器(返回自定义登录页面 HTML)。
  11. DefaultLogoutPageGeneratingFilter

    • DefaultLogoutPageGeneratingFilter 是 Spring Security 中的一个辅助过滤器,作用与 DefaultLoginPageGeneratingFilter 类似 —— 当开发者未自定义登出页面时,自动生成一个默认的 HTML 登出确认页面,用于提示用户确认是否执行登出操作。

    • 核心职责

      1. 检测是否存在自定义登出页:过滤器会判断开发者是否通过配置自定义了登出页面(如 /custom-logout)。如果已配置,此过滤器不生效;如果未配置,它会自动介入。
      2. 生成默认登出确认页面:当用户访问默认登出路径(默认 /logout,GET 方法)时,过滤器会生成一个简单的 HTML 页面,包含:
        • 登出确认提示(如 “是否确定要退出登录?”);
        • 一个提交按钮(点击后发送 POST 请求到 /logout,触发实际登出逻辑);
        • CSRF 令牌隐藏域(因登出是 “修改型操作”,需通过 CSRF 校验,防止恶意登出)。
      3. 处理登出页请求:仅拦截对默认登出路径(/logout)的 GET 请求,返回生成的登出确认页面;对于 POST 请求(实际执行登出),则放行给 LogoutFilter 处理。
    • 工作流程

      1. 用户触发登出:例如用户点击 “退出登录” 链接,前端导航到 /logout(GET 请求)。
      2. 过滤器拦截并生成页面DefaultLogoutPageGeneratingFilter 拦截 GET /logout请求,检测到未配置自定义登出页,生成默认登出确认页面并返回给浏览器。
      3. 用户确认登出:用户在页面中点击 “确认登出” 按钮,表单以 POST /logout 发送请求(携带 CSRF 令牌)。
      4. 执行实际登出逻辑POST /logout 请求被 LogoutFilter 拦截,执行清除上下文、失效会话等登出操作(见 LogoutFilter 详解)。
      5. 登出成功跳转:登出完成后,由 LogoutSuccessHandler 处理后续逻辑(默认重定向到 /login?logout)。
    • 默认登出页面的特点

      • 简单交互:仅包含确认提示和提交按钮,无复杂样式,适合快速测试。
      • CSRF 保护:自动包含 CSRF 令牌隐藏域,确保登出请求是用户主动发起的(防止恶意网站伪造登出请求)。
      • 依赖默认登出路径:仅对默认的 /logout 路径生效,若通过 .logout().logoutUrl("/api/logout") 自定义了登出路径,此过滤器可能不生效。
    • 如何禁用或替换默认登出页?

      在实际项目中,通常不需要默认登出页,可通过以下方式处理:

      1. 使用自定义登出页:配置自定义登出页路径,DefaultLogoutPageGeneratingFilter 会自动失效:

        @Configuration
        @EnableWebSecurity
        public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.logout(logout -> logout// 自定义登出页面路径(GET 请求访问).logoutPage("/custom-logout")// 登出处理路径(POST 请求,实际执行登出).logoutUrl("/do-logout").permitAll());return http.build();}
        }
        

        需确保 /custom-logout 对应一个控制器,返回自定义的登出确认页面。

      2. 前后端分离项目:无页面直接登出:前后端分离项目通常通过 API 直接触发登出(无需确认页面),可禁用默认登出页并配置登出接口:

        @Configuration
        public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.logout(logout -> logout.logoutUrl("/api/logout") // 登出接口(POST 请求).logoutMethod(HttpMethod.POST).logoutSuccessHandler((req, res, auth) -> {// 直接返回 JSON 响应,无需页面跳转res.getWriter().write("{\"success\":true}");}).permitAll());return http.build();}
        }
        
  12. BasicAuthenticationFilter

    • BasicAuthenticationFilter 是 Spring Security 中处理 HTTP Basic 认证 的核心过滤器,专门拦截携带 Authorization: Basic <凭证> 请求头的请求,解析并校验用户名密码,完成认证流程。它适用于简单的内部系统、API 测试或设备间通信场景(无需复杂登录页面)。

    • 核心职责

      1. 拦截并解析认证凭证:检测请求头中是否存在 Authorization: Basic <凭证>,若存在则解码凭证,提取用户名和密码;若不存在或格式不正确,则不处理(后续过滤器可能触发未认证逻辑)。
      2. 执行认证逻辑:构建未认证的 UsernamePasswordAuthenticationToken(包含提取的用户名和密码),交给 AuthenticationManager 执行校验(通过 UserDetailsService 加载用户信息,PasswordEncoder 比对密码)。
      3. 处理认证结果
        • 认证成功:将认证通过的 Authentication 对象存入 SecurityContextHolder,放行请求。
        • 认证失败:触发 AuthenticationEntryPoint,返回 401 Unauthorized 响应,并在响应头中添加 WWW-Authenticate: Basic realm="Realm",提示浏览器弹出登录框。
    • 工作流程

      1. 客户端发起请求:客户端发送请求,携带 Authorization: Basic YWRtaW46MTIzNDU2头。
      2. 过滤器拦截并解码BasicAuthenticationFilter 拦截请求,解码凭证得到用户名 admin 和密码 123456
      3. 执行认证校验:构建 UsernamePasswordAuthenticationToken 并交给 AuthenticationManager,校验用户名密码是否匹配。
      4. 认证成功处理:校验通过后,将认证信息存入 SecurityContextHolder,请求继续传递到后续过滤器和 Controller。
      5. 认证失败处理:若密码错误或用户不存在,返回 401 响应,浏览器弹出登录框让用户重新输入凭证。
      6. 无凭证处理:若请求无 Authorization 头,过滤器不干预,后续 ExceptionTranslationFilter 会触发 AuthenticationEntryPoint,返回 401并提示输入凭证。
    • 代码示例:启用 Basic 认证

      在 Spring Security 中,需通过 .httpBasic() 显式启用 BasicAuthenticationFilter,示例如下:

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated() // 所有请求需认证)// 启用 HTTP Basic 认证.httpBasic(basic -> basic// 自定义认证失败响应(默认返回 401 并提示登录框).authenticationEntryPoint((request, response, authException) -> {response.setContentType("application/json;charset=UTF-8");response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.getWriter().write("{\"error\":\"认证失败:请提供正确的用户名和密码\"}");}));return http.build();}// 配置密码编码器(强制要求)@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 配置用户信息(示例为内存用户)@Beanpublic UserDetailsService userDetailsService() {UserDetails user = User.withUsername("admin").password(passwordEncoder().encode("123456")).roles("ADMIN").build();return new InMemoryUserDetailsManager(user);}
      }
      
    • 注意事项

      1. 安全性较低
        • 凭证仅经过 Base64 编码(非加密),若传输未使用 HTTPS,凭证易被拦截破解。
        • 凭证会随每次请求发送,增加泄露风险。
        • 不支持 “登出”(除非客户端主动清除凭证,或服务端通过其他方式拒绝旧凭证)。
      2. 适用场景:适合内部系统、API 测试、物联网设备通信等对安全性要求不高,且需要简单认证的场景,不推荐用于面向公众的生产系统。
      3. 与表单登录的共存:Spring Security 支持同时启用 Basic 认证和表单登录,客户端可选择任意一种方式认证(浏览器默认优先弹出 Basic 登录框,若用户取消则可能触发表单登录重定向)。
      4. 跨域场景注意:若存在跨域请求,需配合 CorsFilter 配置跨域规则,确保 Authorization 头能被正常传递和处理。
  13. RequestCacheAwareFilter

    • RequestCacheAwareFilter 是 Spring Security 过滤器链中的重要成员,主要负责 缓存用户认证前的原始请求,并在用户成功登录后自动重定向回该请求,提升用户体验。它解决了 “用户访问受保护资源时被拦截到登录页,登录后需手动返回原资源” 的问题。

    • 简单来说,它的核心作用是:“记住” 用户在未登录时想要访问的页面,等用户登录成功后,自动帮用户跳转到那个页面

      具体职责包括:

      1. 缓存未认证请求:当用户未登录却访问受保护资源(如 /dashboard)时,RequestCacheAwareFilter 会将该请求(包含路径、参数等信息)缓存起来(默认存储在 HttpSession 中)。
      2. 登录后重定向到原请求:用户登录成功后,过滤器会从缓存中取出之前保存的请求,将用户重定向到该请求对应的资源(如 /dashboard),而非默认首页。
      3. 避免重复缓存:仅缓存 “需要认证的 GET 请求”(非修改型请求),避免缓存 POST 等可能产生副作用的请求(防止重复提交表单)。
    • 工作流程

      1. 用户访问受保护资源:例如用户未登录时直接访问 /dashboard(需认证)。
      2. 拦截并缓存请求
        • Spring Security 发现用户未认证,触发拦截(由 ExceptionTranslationFilter 处理)。
        • RequestCacheAwareFilter/dashboard 请求的详细信息(URL、参数、请求方法等)缓存到 HttpSession 中(通过 RequestCache 接口实现,默认是 HttpSessionRequestCache)。
      3. 重定向到登录页:用户被重定向到登录页(如 /login)。
      4. 用户登录成功:用户输入正确的用户名密码,完成认证。
      5. 恢复缓存的请求
        • 登录成功后,RequestCacheAwareFilter 从缓存中取出之前保存的 /dashboard 请求。
        • 自动将用户重定向到 /dashboard,而非默认的首页(如 /home)。
      6. 清除缓存:重定向后,缓存的请求会被自动清除,避免重复重定向。
    • 代码示例:自定义缓存行为

      默认情况下,RequestCacheAwareFilter 已能满足大部分需求,也可通过配置自定义其行为:

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/login").permitAll().anyRequest().authenticated()).formLogin(form -> form// 登录成功后,若有缓存请求则重定向到该请求,否则重定向到 /home.defaultSuccessUrl("/home", false) // 禁用缓存(始终重定向到 defaultSuccessUrl)// .successHandler((req, res, auth) -> res.sendRedirect("/home")))// 自定义 RequestCache(可选).requestCache(requestCache -> requestCache.requestCache(customRequestCache()));return http.build();}// 自定义请求缓存(例如只缓存特定路径的请求)@Beanpublic RequestCache customRequestCache() {HttpSessionRequestCache cache = new HttpSessionRequestCache();// 只缓存 /dashboard 路径的请求cache.setRequestMatcher(new AntPathRequestMatcher("/dashboard"));return cache;}
      }
      
      • defaultSuccessUrl("/home", false) 中,false 表示 “若有缓存请求则优先重定向到该请求,否则到 /home”;若设为 true,则始终重定向到 /home(忽略缓存)。
      • 若通过 successHandler 自定义登录成功逻辑,需手动处理缓存请求(如调用 requestCache.getRequest() 获取并重定向)。
    • 注意事项

      1. 只缓存 GET 请求:默认仅缓存 GET、HEAD 等 “安全方法” 的请求,不缓存 POST、PUT 等可能修改数据的请求(防止登录后重复提交表单导致的数据重复)。
      2. 缓存存储位置:默认使用 HttpSessionRequestCache,将请求信息存储在 Session 中,适合有状态应用;无状态应用(如 JWT)可自定义 RequestCache 实现(如存储在 Redis 中)。
      3. 禁用缓存场景:若希望登录后始终跳转到固定页面(如首页),可通过以下方式禁用缓存:
        • 配置 defaultSuccessUrl("/home", true)(强制跳转默认页)。
        • 使用自定义 successHandler 并忽略缓存请求。
  14. SecurityContextHolderAwareRequestFilter

    • SecurityContextHolderAwareRequestFilter 是 Spring Security 中一个请求包装过滤器,它的核心作用是对原始 HttpServletRequest 进行包装,添加与安全相关的便捷方法,让开发者能更简单地在控制器(或其他组件)中获取当前用户的认证信息和权限状态。

    • 核心职责

      1. 包装原始请求:拦截所有请求,将原始 HttpServletRequest 包装为 SecurityContextHolderAwareRequestWrapper,后续过滤器和控制器接收到的都是这个包装后的请求对象。

      2. 提供安全相关快捷方法:包装后的请求对象新增了以下常用方法(这些方法内部会自动从 SecurityContextHolder中获取认证信息):

        方法作用说明
        getUserPrincipal()获取当前登录用户的 Principal 对象(即 Authentication 中的 principal,通常是 UserDetails)。
        getRemoteUser()获取当前登录用户的用户名(字符串形式)。
        isUserInRole(String role)判断当前用户是否拥有指定角色(角色名需省略前缀 ROLE_,如传入 ADMIN 等价于判断 ROLE_ADMIN)。
        isAuthenticated()判断当前用户是否已认证(返回 boolean)。
    • 工作流程

      1. 请求经过过滤器:当请求进入过滤器链并到达 SecurityContextHolderAwareRequestFilter 时,过滤器会拦截请求。
      2. 包装请求对象:过滤器创建 SecurityContextHolderAwareRequestWrapper 实例,将原始 HttpServletRequest 作为参数传入,完成包装。包装后的对象会替代原始请求,继续在过滤器链中传递。
      3. 控制器中使用快捷方法:控制器(@Controller)接收的 HttpServletRequest 实际上是包装后的对象,开发者可直接调用新增的安全方法,无需手动访问 SecurityContextHolder
    • 代码示例:使用包装后的请求方法

      传统方式(未使用过滤器):

      @RestController
      public class UserController {@GetMapping("/user")public String getUserInfo() {// 需手动从 SecurityContextHolder 获取认证信息Authentication auth = SecurityContextHolder.getContext().getAuthentication();String username = auth.getName();boolean isAdmin = auth.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));return "用户名:" + username + ",是否为管理员:" + isAdmin;}
      }
      

      使用 SecurityContextHolderAwareRequestFilter 后的简化方式:

      @RestController
      public class UserController {@GetMapping("/user")public String getUserInfo(HttpServletRequest request) {// 直接通过请求对象的快捷方法获取String username = request.getRemoteUser(); // 获取用户名boolean isAdmin = request.isUserInRole("ADMIN"); // 判断是否有 ADMIN 角色(自动补全 ROLE_ 前缀)Principal principal = request.getUserPrincipal(); // 获取用户 Principal 对象return "用户名:" + username + ",是否为管理员:" + isAdmin;}
      }
      
    • 特点

      1. 透明包装:开发者无需修改控制器代码,只需通过注入的 HttpServletRequest 即可调用新增方法,对现有逻辑无侵入。
      2. 角色名自动补全:调用 isUserInRole("ADMIN") 时,过滤器会自动补全为 ROLE_ADMIN(与 Spring Security 中角色的存储格式一致),简化角色判断。
      3. 依赖 SecurityContext:所有快捷方法的实现都依赖 SecurityContextHolder 中的 SecurityContext,如果用户未认证(如匿名用户),getRemoteUser() 会返回 nullisUserInRole() 会返回 false
  15. AnonymousAuthenticationFilter

    • AnonymousAuthenticationFilter 是 Spring Security 过滤器链中的核心过滤器,它的核心作用是:为未认证的用户(即未登录用户)自动分配一个 “匿名用户” 身份,确保系统中所有请求都有明确的认证状态(已认证或匿名),避免因 “无身份” 导致的逻辑漏洞或 null 指针异常。

    • 核心职责

      1. 检测未认证请求:拦截所有请求,检查 SecurityContextHolder 中是否存在有效的 Authentication 对象(即用户是否已登录)。
      2. 分配匿名身份:若不存在有效认证信息(用户未登录),则自动创建一个 AnonymousAuthenticationToken(匿名认证令牌),包含:
        • 用户名:默认 anonymousUser(可自定义)。
        • 权限:默认 ROLE_ANONYMOUS(可自定义)。
      3. 存储匿名身份:将 AnonymousAuthenticationToken 存入 SecurityContextHolder,使后续过滤器和控制器能识别 “匿名用户” 身份。
    • 工作流程

      1. 用户未登录访问资源:例如用户未登录时访问 /home(公开资源)。
      2. 过滤器检测认证状态AnonymousAuthenticationFilter 检查 SecurityContextHolder,发现没有 Authentication 对象(用户未登录)。
      3. 创建并设置匿名令牌:生成 AnonymousAuthenticationToken(用户名为 anonymousUser,角色为 ROLE_ANONYMOUS),并设置到 SecurityContextHolder中。
      4. 后续组件处理匿名用户
        • 授权过滤器(如 FilterSecurityInterceptor)根据规则判断匿名用户是否有权访问资源(如 permitAll() 允许访问)。
        • 控制器中可通过 SecurityContextHolderHttpServletRequest 方法(如 getRemoteUser())获取匿名用户信息(返回 anonymousUser)。
    • 代码示例:识别匿名用户

      @RestController
      public class HomeController {@GetMapping("/home")public String home() {Authentication auth = SecurityContextHolder.getContext().getAuthentication();if (auth instanceof AnonymousAuthenticationToken) {// 匿名用户(未登录)return "欢迎访问首页,您当前是匿名用户(" + auth.getName() + ")";} else {// 已认证用户return "欢迎回来," + auth.getName() + "!";}}
      }
      
      • 未登录用户访问 /home:返回 欢迎访问首页,您当前是匿名用户(anonymousUser)
      • 登录用户访问 /home:返回 欢迎回来,admin!

      默认的匿名用户名和角色可通过配置修改:

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/home").permitAll() // 允许匿名用户访问首页.anyRequest().authenticated())// 自定义匿名用户配置.anonymous(anonymous -> anonymous.username("guest") // 自定义匿名用户名(默认 anonymousUser).authorities("ROLE_GUEST") // 自定义匿名用户角色(默认 ROLE_ANONYMOUS).principal("guest-principal") // 自定义 principal 对象);return http.build();}
      }
      
      • 配置后,未登录用户的用户名为 guest,角色为 ROLE_GUEST,控制器中可获取到这些自定义信息。
    • 注意事项

      1. 匿名用户也是 “已认证” 的一种特殊状态AnonymousAuthenticationTokenisAuthenticated() 方法返回 true(标记为已认证),但本质是 “匿名认证”。这是为了统一处理逻辑 —— 无论是真实用户还是匿名用户,SecurityContext 中都有非 nullAuthentication 对象。

      2. permitAll() 的关系permitAll() 允许所有用户访问,包括匿名用户(ROLE_ANONYMOUS)和已认证用户。若需仅允许匿名用户访问(如登录页),可结合 anonymous() 配置:

        .authorizeHttpRequests(auth -> auth.requestMatchers("/login").anonymous() // 仅匿名用户可访问登录页(已登录用户会被拒绝)
        )
        
      3. 禁用匿名用户:通常不建议禁用,但可通过 anonymous().disable() 关闭匿名用户功能。禁用后,未登录用户的 SecurityContext 中会没有 Authentication 对象,需手动处理 null 情况。

  16. SessionManagementFilter

    • SessionManagementFilter 是 Spring Security 中负责 会话安全管理 的核心过滤器,主要处理与用户会话相关的安全策略,如会话并发控制、会话超时处理、防御会话固定攻击等,确保用户会话的安全性和合规性。

    • 核心职责

      会话(HttpSession)是 Web 应用中保存用户状态的关键机制,但也存在安全风险(如会话劫持、多设备同时登录等)。SessionManagementFilter 的核心职责是通过一系列策略管控会话生命周期,具体包括:

      1. 会话并发控制:限制同一用户的最大并发会话数(如禁止同一账号在多设备同时登录)。
      2. 会话固定攻击防御:用户登录时更换会话 ID(Session ID),防止攻击者利用初始会话 ID 劫持会话。
      3. 会话超时 / 失效处理:检测到无效会话(如超时、被强制下线)时,触发指定策略(如重定向到登录页)。
      4. 会话创建策略执行:根据配置的会话创建策略(如 “需要时创建”“从不创建”)管理会话的创建时机。
    • 工作流程

      1. 拦截请求并检查会话状态:过滤器拦截所有请求,从 SecurityContextHolder 中获取当前用户的认证信息(Authentication),并检查当前会话(HttpSession)的状态。
      2. 处理会话并发:若配置了并发会话限制(如 maximumSessions(1)),过滤器会检查当前用户的活跃会话数:
        • 未超过限制:正常处理请求。
        • 超过限制:根据策略处理(如让最早的会话失效,或拒绝新登录),并触发 SessionInformationExpiredStrategy(如提示 “账号在其他设备登录”)。
      3. 防御会话固定攻击:当用户完成登录(从匿名状态变为已认证状态)时,过滤器会触发会话 ID 更换(session.regenerateId()),并将旧会话中的数据迁移到新会话,使攻击者持有的旧会话 ID 失效。
      4. 检测会话失效:若会话已超时或被标记为失效(如管理员强制踢下线),过滤器会清除 SecurityContextHolder 中的认证信息,并触发 InvalidSessionStrategy(如重定向到 /login?invalid-session)。
      5. 执行会话创建策略:根据配置的 sessionCreationPolicy(如 IF_REQUIRED“需要时创建”、NEVER“从不创建”)决定是否创建新会话,避免不必要的会话创建(如无状态应用)。
    • 代码示例:自定义会话管理策略

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).formLogin(Customizer.withDefaults())// 配置会话管理.sessionManagement(session -> session// 会话并发控制:最多允许1个并发会话.maximumSessions(1)// 超过并发限制时,是否拒绝新登录(true:拒绝;false:让旧会话失效).maxSessionsPreventsLogin(false)// 并发会话失效策略(如旧会话被踢时的处理).expiredSessionStrategy(event -> {HttpServletResponse response = event.getResponse();response.setContentType("text/html;charset=UTF-8");response.getWriter().write("您的账号在其他设备登录,已被迫下线!<a href='/login'>重新登录</a>");}).and()// 会话固定攻击防御:登录时更换会话ID(默认启用).sessionFixation().migrateSession()// 会话创建策略:仅在需要时创建会话(默认值).sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)// 会话超时处理策略(默认1800秒,即30分钟).invalidSessionStrategy((request, response) -> {response.sendRedirect("/login?session-expired");}));return http.build();}
      }
      
      1. 会话并发控制
        • maximumSessions(n):限制同一用户最多 n 个并发会话(n=1 表示单设备登录)。
        • maxSessionsPreventsLogin(true):超过限制时拒绝新登录(返回 401);false(默认)则使最早的会话失效。
      2. 会话固定攻击防御
        • sessionFixation().migrateSession()(默认):登录时创建新会话并迁移数据,旧会话失效。
        • sessionFixation().newSession():登录时创建全新会话(不迁移数据)。
        • sessionFixation().none():禁用防御(不推荐,存在安全风险)。
      3. 会话创建策略
        • SessionCreationPolicy.IF_REQUIRED(默认):需要时创建会话(如登录后)。
        • SessionCreationPolicy.ALWAYS:每次请求都创建会话(不推荐,浪费资源)。
        • SessionCreationPolicy.NEVER:从不主动创建会话,但如果请求带来会话则使用。
        • SessionCreationPolicy.STATELESS:完全无状态(不使用会话,适合 JWT 等令牌认证)。
      4. 会话超时:默认超时时间由 Servlet 容器配置(如 Tomcat 默认 30 分钟),可通过 server.servlet.session.timeout=30m(Spring Boot)修改。超时后由 invalidSessionStrategy 处理(如重定向到登录页)。
    • 注意事项

      • 传统 Web 应用:依赖会话存储认证状态,需通过 SessionManagementFilter 配置并发控制、超时策略等,保障会话安全。
      • 前后端分离 + JWT:通常采用 STATELESS 策略(禁用会话),此时该过滤器作用有限(但仍会处理剩余的会话相关逻辑)。
      • 会话共享:分布式系统中会话共享(如 Redis 存储)时,需确保 SessionManagementFilter 的策略在集群中生效(如并发控制依赖共享存储)。
  17. ExceptionTranslationFilter

    • ExceptionTranslationFilter 是 Spring Security 过滤器链中专门处理认证与授权异常的核心过滤器,它像一个 “安全异常处理器”,负责捕获过滤器链中抛出的安全相关异常(如未登录、权限不足),并将其转换为用户友好的响应(如重定向到登录页、返回 403 错误),避免异常直接暴露给用户或导致系统崩溃。

    • 核心职责

      1. 捕获安全相关异常:拦截过滤器链中(主要是后续的 FilterSecurityInterceptor 等)抛出的 AuthenticationException(认证异常)和 AccessDeniedException(授权异常)。
      2. 区分异常类型并处理
        • 认证异常(如未登录、登录失败):引导用户进行认证(如重定向到登录页、返回 401 提示)。
        • 授权异常(如已登录但无权限):返回权限不足的响应(如 403 Forbidden)。
      3. 暂存请求信息:对于因未认证被拦截的请求,会暂存请求信息(通过 RequestCache),以便用户登录后能重定向回原请求(配合 RequestCacheAwareFilter)。
    • 工作流程

      1. 拦截请求并执行后续过滤器ExceptionTranslationFilter 本身不处理请求,而是先执行过滤器链中的后续过滤器(如 FilterSecurityInterceptor)。
      2. 捕获异常:若后续过滤器抛出 AuthenticationExceptionAccessDeniedException,则被该过滤器捕获。
      3. 处理认证异常(AuthenticationException
        • 例如:用户未登录访问 /dashboardFilterSecurityInterceptor 发现未认证,抛出 AuthenticationException
        • 过滤器调用 AuthenticationEntryPoint(认证入口点):
          • 传统 Web 应用:默认重定向到登录页(如 /login?login_error),并暂存原请求(/dashboard)。
          • 前后端分离应用:可自定义返回 401 Unauthorized JSON 响应(如 {"error": "请先登录"})。
      4. 处理授权异常(AccessDeniedException
        • 例如:用户已登录但无 ADMIN 角色,却访问 /admin,抛出 AccessDeniedException
        • 过滤器判断用户是否已认证:
          • 已认证但无权限:调用 AccessDeniedHandler,默认返回 403 Forbidden
          • 未认证(如匿名用户):先执行认证流程(同步骤 3),再判断权限。
    • 代码示例:自定义异常处理

      可通过配置自定义认证 / 授权异常的处理逻辑,适应不同场景(如前后端分离返回 JSON):

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/login").permitAll().requestMatchers("/admin").hasRole("ADMIN").anyRequest().authenticated()).formLogin(Customizer.withDefaults())// 配置异常处理.exceptionHandling(ex -> ex// 处理认证异常(未登录).authenticationEntryPoint((request, response, authException) -> {// 前后端分离:返回 401 JSONresponse.setContentType("application/json;charset=UTF-8");response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.getWriter().write("{\"error\":\"请先登录\", \"code\":401}");})// 处理授权异常(权限不足).accessDeniedHandler((request, response, accessDeniedException) -> {// 前后端分离:返回 403 JSONresponse.setContentType("application/json;charset=UTF-8");response.setStatus(HttpServletResponse.SC_FORBIDDEN);response.getWriter().write("{\"error\":\"权限不足\", \"code\":403}");}));return http.build();}
      }
      
      1. AuthenticationEntryPoint:处理未认证异常的策略接口,默认实现是 LoginUrlAuthenticationEntryPoint(重定向到登录页)。自定义实现可返回 JSON、跳转自定义页面等。
      2. AccessDeniedHandler:处理授权异常的策略接口,默认实现是 AccessDeniedHandlerImpl(返回 403 错误页面)。自定义实现可返回 JSON、记录审计日志等。
      3. RequestCache 配合:处理未认证异常时,会通过 RequestCache 暂存当前请求信息(如 /dashboard),用户登录成功后,RequestCacheAwareFilter 会将其重定向回该请求。
  18. FilterSecurityInterceptor

    • FilterSecurityInterceptor 是 Spring Security 过滤器链中的最后一道防线,也是负责最终权限校验的核心过滤器。它的核心作用是:根据开发者配置的安全规则(如 “哪些路径需要哪些权限”),对请求进行权限判断,决定是否允许访问目标资源。

    • 核心职责

      1. 获取当前请求的安全规则:根据请求的路径(如 /admin),匹配开发者配置的安全规则(如 hasRole("ADMIN")),确定访问该资源所需的权限。
      2. 提取当前用户的权限:从 SecurityContextHolder 中获取当前登录用户的认证信息(Authentication),提取用户拥有的权限(Authorities)。
      3. 执行权限判断:通过 AccessDecisionManager(访问决策管理器)对比 “资源所需权限” 与 “用户拥有的权限”,决定是否允许访问:
        • 权限匹配:放行请求,允许访问目标资源(如控制器方法)。
        • 权限不匹配:抛出 AccessDeniedException(授权异常),由 ExceptionTranslationFilter 处理(返回 403 错误)。
    • 工作流程

      1. 请求到达过滤器链末尾:经过前面的过滤器(如认证、会话管理等)后,请求到达 FilterSecurityInterceptor(过滤器链的最后一步)。

      2. 获取请求对应的安全配置

        过滤器通过 SecurityMetadataSource(安全元数据源)解析当前请求的路径(如 /admin),找到对应的安全规则(如 hasRole("ADMIN"))。

      3. 获取用户权限:从 SecurityContextHolder 中获取 Authentication 对象,提取用户的权限列表(如 ROLE_USER)。

      4. 权限判断

        • 将 “资源所需权限”(ROLE_ADMIN)和 “用户拥有的权限”(ROLE_USER)交给 AccessDecisionManager
        • AccessDecisionManager 对比后发现权限不匹配,抛出 AccessDeniedException
      5. 处理权限不足

        • 异常被 ExceptionTranslationFilter 捕获,返回 403 Forbidden 响应(或自定义提示)。
        • 若权限匹配,则放行请求,最终到达 DispatcherServlet 并调用对应的控制器方法。
    • 代码示例:定义安全规则

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth// 公开资源:允许所有用户访问.requestMatchers("/", "/public/**").permitAll()// 管理员资源:仅 ADMIN 角色可访问.requestMatchers("/admin/**").hasRole("ADMIN")// 用户资源:仅 USER 或 ADMIN 角色可访问.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")// 其他所有资源:需认证(登录即可,无特定角色要求).anyRequest().authenticated()).formLogin(Customizer.withDefaults());return http.build();}
      }
      
      1. SecurityMetadataSource:负责解析请求路径与安全规则的映射关系(如 /admin/** 对应 hasRole("ADMIN")),默认实现是 ExpressionBasedFilterInvocationSecurityMetadataSource(支持 SpEL 表达式,如 hasRole()permitAll())。
      2. AccessDecisionManager:权限决策的核心组件,根据 “资源所需权限” 和 “用户拥有的权限” 决定是否允许访问。默认使用 AffirmativeBased(只要有一个决策器同意就允许访问),包含三个决策器:
        • RoleVoter:判断用户角色是否匹配(如 ROLE_ADMIN)。
        • AuthenticatedVoter:判断用户是否已认证(如 isAuthenticated())。
        • WebExpressionVoter:支持 SpEL 表达式判断(如 hasPermission())。
      3. RunAsManager:可选组件,用于临时切换用户身份(如某些操作需要以管理员身份执行),一般很少使用。
      4. 当用户(ROLE_USER)访问 /admin/dashboard 时,FilterSecurityInterceptor 会匹配到 hasRole("ADMIN") 规则,发现用户权限不足,抛出 AccessDeniedException,最终返回 403。
      5. 当用户(ROLE_ADMIN)访问 /admin/dashboard 时,权限匹配,请求被放行。
    • 扩展场景:自定义权限判断

      如果默认的权限判断逻辑无法满足需求(如基于数据库的动态权限),可通过自定义 SecurityMetadataSourceAccessDecisionManager 实现:

      // 示例:自定义 AccessDecisionManager(简化版)
      public class CustomAccessDecisionManager implements AccessDecisionManager {@Overridepublic void decide(Authentication auth, Object object, Collection<ConfigAttribute> attrs) throws AccessDeniedException {// 1. 获取用户权限(auth.getAuthorities())// 2. 获取资源所需权限(attrs)// 3. 自定义逻辑判断(如从数据库查询动态权限)boolean hasPermission = checkPermission(auth, attrs);if (!hasPermission) {throw new AccessDeniedException("权限不足");}}private boolean checkPermission(Authentication auth, Collection<ConfigAttribute> attrs) {// 实现自定义权限校验逻辑// ...}// 其他方法省略(supports)
      }// 在配置中替换默认决策管理器
      @Configuration
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).with(new Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry>() {@Overridepublic void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {registry.accessDecisionManager(new CustomAccessDecisionManager());}});return http.build();}
      }
      

执行流程举例(以表单登录为例)

  1. 请求受保护资源 /dashboard
    → 被 FilterSecurityInterceptor 拦下:未认证
    → 抛出 AuthenticationException

  2. ExceptionTranslationFilter 捕获异常
    → 重定向 /login

  3. 用户提交 /login
    UsernamePasswordAuthenticationFilter 捕获请求
    → 交由 AuthenticationManager 执行认证
    → 成功 → SecurityContext 写入认证信息

  4. 登录成功后
    RequestCacheAwareFilter 重定向回最初请求的页面
    → 后续请求经过 SecurityContextHolderFilter 自动加载认证信息

  5. 用户登出 /logout
    LogoutFilter 清理 SecurityContext、Session、Cookie

自定义过滤器的添加与位置控制

在 Spring Security 中,请求进入系统后,会依次经过十几层安全过滤器(比如认证、授权、异常处理、登出等)。

而我们可以在这些默认过滤器之间 插入自定义 Filter —— 实现日志记录、验证码校验、请求签名验证、黑名单校验等个性化安全逻辑。

核心 API:三种添加方式

Spring Security 提供了 3 种方式来控制你自定义过滤器在链中的位置:

方法作用示例
addFilterBefore(filter, class)把自定义过滤器放在指定过滤器 之前验证码验证放在登录过滤器前
addFilterAfter(filter, class)放在指定过滤器 之后日志过滤放在认证后
addFilterAt(filter, class)直接替换指定过滤器替换默认的用户名密码过滤器

示例:自定义日志过滤器

public class LoggingFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)throws ServletException, IOException {String uri = request.getRequestURI();Authentication auth = SecurityContextHolder.getContext().getAuthentication();String username = (auth != null && auth.isAuthenticated())? auth.getName(): "匿名用户";System.out.println("请求路径: " + uri + " | 当前用户: " + username);filterChain.doFilter(request, response);}
}
  • 继承 OncePerRequestFilter:保证每次请求只执行一次;
  • 记录请求路径:通过 request.getRequestURI() 获取当前请求的 URI(如 /api/user)。
  • 获取当前用户
    • SecurityContextHolder.getContext() 中获取 Authentication 对象(Spring Security 存储用户认证信息的核心对象)。
    • 判断用户是否已认证(auth != null && auth.isAuthenticated()):
      • 已认证:通过 auth.getName() 获取用户名(如登录用户 admin)。
      • 未认证:标记为 “匿名用户”(注意:如果启用了 AnonymousAuthenticationFilter,未登录用户会被分配 anonymousUser 身份,此时 auth.getName() 会返回 anonymousUser,而非这里的 “匿名用户” 字符串,可根据需求调整)。
  • 打印日志:输出 “请求路径 + 当前用户” 的日志信息,然后通过 filterChain.doFilter()放行请求,保证过滤器链继续执行。

在配置类中注册过滤器

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Autowiredprivate LoggingFilter loggingFilter;@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).formLogin(withDefaults());// 添加到 UsernamePasswordAuthenticationFilter 之前http.addFilterBefore(loggingFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}
}
  • @EnableWebSecurity:开启 Spring Security 的核心功能,自动注册 Security 相关的过滤器链和组件。

  • authorizeHttpRequests(auth -> auth.anyRequest().authenticated()):配置所有请求(anyRequest())都需要认证(authenticated())才能访问,即未登录用户访问任何接口都会被拦截到登录页。

  • formLogin(withDefaults()):启用默认的表单登录功能:

    • 提供默认登录页(/login,GET 请求访问)。
    • 登录表单提交到 /login(POST 请求),由 UsernamePasswordAuthenticationFilter 处理认证。
    • 认证成功后默认重定向到之前访问的受保护页面(或 / 首页)。
  • addFilterBefore(loggingFilter, UsernamePasswordAuthenticationFilter.class):将自定义的 LoggingFilter 插入到 Security 过滤器链中,且位于 UsernamePasswordAuthenticationFilter 之前。

    这意味着:LoggingFilter 会先于 “用户名密码认证过滤器” 执行,即在用户提交的登录请求被认证处理前,就会记录请求日志

FilterChainProxy

FilterChainProxy 是什么?

在 Spring Security 中,所有的安全过滤逻辑都最终交给一个“超级过滤器”来统一调度,那就是:

org.springframework.security.web.FilterChainProxy

它的作用是:

将不同的安全配置(即不同的 SecurityFilterChain)映射到不同的请求 URL。

换句话说:

它是整个安全过滤体系的“总指挥官”,负责把每一个 HTTP 请求分配到正确的安全规则链上去执行。

可以这样理解:

在这里插入图片描述

FilterChainProxy 的主要职责

FilterChainProxy 主要做三件事:

任务说明
路由根据请求 URL 找到匹配的 SecurityFilterChain
调度执行该链上定义的一组过滤器
保护处理异常与上下文清理,确保安全上下文一致性

核心成员结构

  1. 关键字段

    public class FilterChainProxy extends GenericFilterBean {private final List<SecurityFilterChain> filterChains;// SecurityContext 管理过滤器上下文private FilterChainProxy.FilterChainValidator filterChainValidator;...
    }
    

    filterChains 就是所有安全链的集合,类型是:

    List<SecurityFilterChain>
    
  2. SecurityFilterChain 接口

    public interface SecurityFilterChain {boolean matches(HttpServletRequest request);List<Filter> getFilters();
    }
    
    • matches() 判断当前请求是否命中该安全链。
    • getFilters() 返回该链中要执行的过滤器。

执行流程

FilterChainProxy 的执行流程可概括为 “接收请求 → 匹配过滤器链 → 执行链中过滤器 → 放行请求”,具体步骤如下:

  1. 接收请求,进入 doFilter 方法

    当请求到达 Servlet 容器时,FilterChainProxy 作为注册的过滤器之一,其 doFilter(ServletRequest, ServletResponse, FilterChain) 方法被调用。

    • 参数 FilterChainServlet 容器的原始过滤器链(包含非 Spring Security 的其他过滤器,如日志过滤器、编码过滤器等)。
  2. 构建 FirewalledRequest 包装请求(安全加固)

    FilterChainProxy 首先通过 HttpFirewall(默认 StrictHttpFirewall)对原始请求进行包装,生成 FirewalledRequest

    • 作用:防御恶意请求(如包含特殊字符的 URL、非法 HTTP 方法),若请求非法则直接拒绝(抛出异常)。
  3. 匹配最合适的 SecurityFilterChain

    FilterChainProxy 内部持有多个 SecurityFilterChain 实例(由开发者通过 @Bean定义,如 SecurityFilterChain 配置类),它会遍历这些链,找到第一个与当前请求匹配的链:

    • 匹配规则:每个 SecurityFilterChain 通过 requestMatcher 定义其负责的 URL 路径(如 /api/**),FilterChainProxy 调用 SecurityFilterChain.matches(request) 判断是否匹配。
    • 示例:若请求路径为 /api/user,则匹配 requestMatcher("/api/**")SecurityFilterChain
  4. 执行匹配到的 SecurityFilterChain 中的过滤器

    找到匹配的 SecurityFilterChain 后,FilterChainProxy 会创建一个内部过滤器链执行器(VirtualFilterChain,并执行该链中的所有 Spring Security 过滤器(如 CsrfFilterUsernamePasswordAuthenticationFilter 等):

    • VirtualFilterChain 是 Spring Security 内部的过滤器链实现,它会按顺序执行 SecurityFilterChain 中的过滤器。
    • 每个过滤器执行完成后,通过 filterChain.doFilter() 触发下一个过滤器,直至链中所有过滤器执行完毕。
  5. 执行 Servlet 容器的原始过滤器链

    SecurityFilterChain 中的所有过滤器执行完毕后,VirtualFilterChain 会调用原始的 Servlet 过滤器链(步骤 1 中的 FilterChain),让请求继续经过容器中的其他过滤器(如自定义的日志过滤器、Spring MVC 的 DispatcherServlet),最终到达目标资源(如控制器方法)。

  6. 处理响应(可选)

    请求处理完成后,响应会按过滤器链的反向顺序返回,每个过滤器可在响应返回时进行额外处理(如添加响应头、记录日志)。

组件说明

  1. SecurityFilterChain:每个 SecurityFilterChain 包含一组 Filter(Spring Security 核心过滤器,如认证、授权过滤器)和一个 RequestMatcher(匹配请求路径)。开发者可通过 @Bean 定义多个 SecurityFilterChain,实现不同路径的差异化安全配置。

    示例:定义两个过滤器链,分别处理 /api/**/admin/**

    @Configuration
    public class SecurityConfig {// 处理 /api/** 路径的过滤器链@Beanpublic SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {http.requestMatchers(m -> m.antMatchers("/api/**")).authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).httpBasic(Customizer.withDefaults());return http.build();}// 处理 /admin/** 路径的过滤器链@Beanpublic SecurityFilterChain adminFilterChain(HttpSecurity http) throws Exception {http.requestMatchers(m -> m.antMatchers("/admin/**")).authorizeHttpRequests(auth -> auth.anyRequest().hasRole("ADMIN")).formLogin(Customizer.withDefaults());return http.build();}
    }
    
  2. HttpFirewall:用于请求安全校验,默认实现 StrictHttpFirewall 会禁止包含分号(;)、反斜杠(\)等特殊字符的 URL,防止路径遍历攻击。若需允许特殊字符,可自定义 HttpFirewall

http://www.dtcms.com/a/503842.html

相关文章:

  • 小区媒体网站建设wordpress简易主题
  • 手机网站经典案例wordpress负载均衡上传附件
  • 银川网站建站中企动力做的网站价格区间
  • 兴义网站开发杭州赛虎网站建设
  • 数据库基础概念体系梳理
  • Kotlin Flow 的使用
  • 网站如何做seo上海网站推广方法
  • Qwen2.5技术报告解读:Qwen2.5 Technical Report
  • 操作系统:进程同步问题(一)
  • Linux---终端 I/O 控制接口开发<termios.h>终端输入输出系统
  • Linux 之 【Linux权限】
  • 网站建设策划书范文案例重庆百度关键词推广
  • 健身器材 网站模版wordpress用户系统
  • 两款实用工具分享:下载与网速测试的轻量级解决方案
  • DVWA靶场实战:Web四大经典漏洞攻防全解析
  • 海外网站测速本地网站建设开发信息大全
  • PowerCat命令操作:PowerShell版的Netcat在渗透测试中的应用
  • 域名注册最好的网站南京设计公司前十名
  • 快速定位源码问题:SourceMap的生成/使用/文件格式与历史
  • 湖南移动官网网站建设wordpress 菜单 分隔
  • 邯郸网站建设服务报价全国住房和城乡建设厅官网
  • leetcode 143 重排链表
  • 元宇宙与职业教育的深度融合:重构技能培养的实践与未来
  • 坪山网站建设哪家便宜帝国cms 网站地图 自定义
  • 双拼输入法:提升打字效率的另一种选择
  • 如何做论坛网站 知乎整套网页模板
  • XSS平台xssplatform搭建
  • SQL入门(structured query language)
  • SAP SD客户主数据查询接口分享
  • RedPlayer 视频播放器在 HarmonyOS 应用中的实践