Spring Security 核心过滤器详解:过滤器链的工作机制与职责分工 Spring Security 的本质是一个过滤器链 ,所有 Web 安全功能(认证、授权、CSRF 防护等)均通过不同职责的过滤器协同实现。每个过滤器专注于单一功能,按特定顺序组成链,确保请求在到达业务代码前完成安全校验。从 “职责定义→工作流程→关键源码→使用场景” 四个维度,彻底讲透每个过滤器的作用与协作逻辑。
过滤器链的整体执行顺序 在分析单个过滤器前,需先明确过滤器链的默认执行顺序 (Spring Security 自动维护,顺序不可随意调整),这是理解安全流程的基础:
执行顺序
过滤器名称
核心职责
1
SecurityContextPersistenceFilter
管理 SecurityContext(创建 / 清空)
2
CsrfFilter
防止 CSRF 攻击(验证 CSRF Token)
3
LogoutFilter
处理退出登录请求
4
UsernamePasswordAuthenticationFilter
处理表单登录(用户名 / 密码认证)
5
AnonymousAuthenticationFilter
为未认证用户分配匿名身份
6
SessionManagementFilter
管理 Session(防固定攻击、限制会话数量)
7
ExceptionTranslationFilter
捕获并处理认证 / 授权异常
8
FilterSecurityInterceptor
权限校验(判断用户是否有权访问资源)
注:实际项目中可能存在更多过滤器(如 RememberMeAuthenticationFilter、OAuth2 相关过滤器),但上述 8 个是最核心的基础过滤器。
核心过滤器详解 1. SecurityContextPersistenceFilter:安全上下文的 “管家” 核心职责 :在请求开始时创建 SecurityContext(存储用户认证信息),在请求结束时清空 SecurityContextHolder(避免 ThreadLocal 内存泄漏),是整个安全流程的 “基础保障”。
工作流程
请求到达时:
从 HttpSession 中读取 SecurityContext(若存在,说明用户已登录);
若不存在,创建新的 SecurityContext;
将 SecurityContext 存入 SecurityContextHolder(ThreadLocal 存储,供后续过滤器使用)。
请求结束时:
若 SecurityContext 有变化(如用户刚登录),将其写入 HttpSession;
调用 SecurityContextHolder.clearContext(),清空当前线程的 SecurityContext。
关键源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public void doFilter (ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (request.getAttribute(FILTER_APPLIED) != null ) { chain.doFilter(request, response); return ; } request.setAttribute(FILTER_APPLIED, Boolean.TRUE); try { SecurityContext contextBeforeChainExecution = repo.loadContext(new HttpRequestResponseHolder (request, response)); SecurityContextHolder.setContext(contextBeforeChainExecution); chain.doFilter(request, response); } finally { SecurityContextHolder.clearContext(); repo.saveContext(SecurityContextHolder.getContext(), request, response); } }
为什么重要?
SecurityContext 是整个安全流程的 “数据载体”,后续过滤器(如 UsernamePasswordAuthenticationFilter、FilterSecurityInterceptor)均依赖它获取用户信息;
清空 SecurityContextHolder 是为了避免 ThreadLocal 内存泄漏(线程池复用线程时,残留的 SecurityContext 会导致用户信息错乱)。
2. CsrfFilter:防跨站请求伪造(CSRF)的 “卫士” 核心职责 :验证请求中的 CSRF Token,防止恶意网站利用用户已登录的会话发起非法请求(如伪造转账、修改密码)。Spring Security 4+ 默认为所有非 GET、HEAD、OPTIONS、TRACE 的请求开启 CSRF 防护。
工作原理
生成 CSRF Token :用户登录时,Spring Security 生成一个随机的 CSRF Token,存入 HttpSession 和 SecurityContext,并通过表单隐藏域或 Cookie 传递给前端;
验证 CSRF Token:
前端提交非安全请求(POST、PUT、DELETE 等)时,需在请求头(如 X-CSRF-TOKEN)或请求参数中携带 CSRF Token;
CsrfFilter 从请求中提取 Token,与 HttpSession 中的 Token 对比,不一致则拒绝请求(返回 403 Forbidden)。
关键源码(简化版) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(HttpServletResponse.class.getName(), response); CsrfToken csrfToken = this .tokenRepository.loadToken(request); boolean missingToken = (csrfToken == null ); if (missingToken) { csrfToken = this .tokenRepository.generateToken(request); this .tokenRepository.saveToken(csrfToken, request, response); } request.setAttribute(CsrfToken.class.getName(), csrfToken); request.setAttribute(csrfToken.getParameterName(), csrfToken); if (!this .requireCsrfProtectionMatcher.matches(request)) { filterChain.doFilter(request, response); return ; } String actualToken = request.getHeader(csrfToken.getHeaderName()); if (actualToken == null ) { actualToken = request.getParameter(csrfToken.getParameterName()); } if (!csrfToken.getToken().equals(actualToken)) { throw new InvalidCsrfTokenException (csrfToken, actualToken); } filterChain.doFilter(request, response); }
常见场景与配置
3. LogoutFilter:处理退出登录的 “执行者” 核心职责 :拦截退出登录请求(默认 /logout),执行退出逻辑(销毁 Session、清空 SecurityContext、删除 “记住我” Cookie 等)。
工作流程
拦截请求 :匹配退出登录路径(默认 /logout,可通过 http.logout().logoutUrl("/custom-logout") 自定义);
执行退出逻辑:
调用 LogoutHandler 链(如 SecurityContextLogoutHandler 清空 SecurityContext、CookieClearingLogoutHandler 删除 Cookie);
触发 LogoutSuccessHandler(默认重定向到登录页,可自定义为返回 JSON 结果);
重定向或返回结果 :退出成功后,默认重定向到 /login?logout,可通过 logoutSuccessUrl("/index") 自定义。
关键配置示例 1 2 3 4 5 6 7 8 9 @Override protected void configure (HttpSecurity http) throws Exception { http.logout() .logoutUrl("/user/logout" ) .logoutSuccessUrl("/login?success" ) .invalidateHttpSession(true ) .deleteCookies("JSESSIONID" , "remember-me" ) .permitAll(); }
4. UsernamePasswordAuthenticationFilter:表单登录的 “核心” 核心职责 :拦截表单登录请求(默认 /login 的 POST 请求),提取用户名 / 密码,封装成 UsernamePasswordAuthenticationToken,委托 AuthenticationManager 完成认证,是表单认证流程的入口 。
工作流程(回顾认证流程核心)
拦截请求 :仅处理 POST 方法的 /login 请求(可通过 loginProcessingUrl("/user/login") 自定义);
提取凭证 :从请求参数中获取用户名(默认 username)和密码(默认 password);
封装 Token :创建未认证的 UsernamePasswordAuthenticationToken(setAuthenticated(false));
委托认证 :调用 AuthenticationManager.authenticate(token),触发后续认证逻辑(如 DaoAuthenticationProvider 校验密码);
处理结果:
认证成功:调用 successfulAuthentication(),将认证信息存入 SecurityContext,重定向到首页;
认证失败:调用 unsuccessfulAuthentication(),清空 SecurityContext,重定向到登录页并显示错误。
关键源码(核心方法) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public Authentication attemptAuthentication (HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST" )) { throw new AuthenticationServiceException ("不支持的认证方法:" + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); username = (username != null ) ? username.trim() : "" ; password = (password != null ) ? password : "" ; UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken (username, password); setDetails(request, authRequest); return this .getAuthenticationManager().authenticate(authRequest); }
自定义配置示例 1 2 3 4 5 6 7 8 9 10 11 @Override protected void configure (HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login.html" ) .loginProcessingUrl("/user/login" ) .usernameParameter("userName" ) .passwordParameter("passWord" ) .defaultSuccessUrl("/home" ) .failureUrl("/login.html?error" ) .permitAll(); }
5. AnonymousAuthenticationFilter:匿名用户的 “身份卡” 核心职责 :为未认证的用户分配一个 “匿名身份”(AnonymousAuthenticationToken),让 Spring Security 的认证流程能统一处理 “已登录用户” 和 “未登录用户”,避免流程断裂。
为什么需要匿名身份? Spring Security 的所有请求都需经过 “认证流程”,若用户未登录,SecurityContext 中无 Authentication 对象,后续过滤器(如 FilterSecurityInterceptor)会无法判断用户身份,导致流程异常。匿名身份本质是 “占位符”,让流程能正常执行。
工作流程
检查身份 :从 SecurityContextHolder 中获取 Authentication;
分配匿名身份 :若 Authentication 为 null(用户未登录),创建 AnonymousAuthenticationToken(包含默认角色 ROLE_ANONYMOUS),存入 SecurityContext;
执行后续流程 :匿名身份会被后续过滤器识别,按 “匿名用户” 的权限规则处理(如允许访问 /login,拒绝访问 /admin)。
关键源码(简化版) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public void doFilter (ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (SecurityContextHolder.getContext().getAuthentication() == null ) { Authentication anonymousAuth = new AnonymousAuthenticationToken ( key, anonymousUser, authorities); SecurityContextHolder.getContext().setAuthentication(anonymousAuth); } chain.doFilter(request, response); }
匿名用户的权限控制 可通过 permitAll() 允许匿名用户访问特定路径,或通过 anonymous() 明确指定:
1 2 3 4 5 6 7 @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/login" , "/register" ).permitAll() .antMatchers("/anonymous/**" ).anonymous() .anyRequest().authenticated(); }
6. SessionManagementFilter:Session 的 “管理员” 核心职责 :管理 HTTP Session,主要处理两大问题:
Session 固定攻击(Session Fixation) :防止攻击者通过固定 Session ID 劫持用户会话;
会话数量控制 :限制同一用户同时登录的会话数量(如只允许一个设备登录)。
核心功能:防 Session 固定攻击 Session 固定攻击流程:攻击者诱导用户使用攻击者生成的 Session ID 登录,登录后攻击者用相同 Session ID 即可劫持会话。SessionManagementFilter 通过以下方式防御:
核心功能:限制会话数量 1 2 3 4 http.sessionManagement() .maximumSessions(1 ) .expiredUrl("/login?expired" ) .maxSessionsPreventsLogin(true );
7. ExceptionTranslationFilter:异常的 “翻译官” 核心职责 :捕获并处理认证 / 授权过程中抛出的异常(如 AuthenticationException、AccessDeniedException),将异常 “翻译” 为用户可理解的响应(如重定向到登录页、返回 403 页面),是安全流程的 “异常处理中心”。
工作流程
捕获异常 :通过 try-catch 捕获后续过滤器链抛出的异常;
分类处理:
认证异常(AuthenticationException) :如用户名密码错误、用户不存在,重定向到登录页;
授权异常(AccessDeniedException) :如用户已登录但无权限,若用户是匿名用户则重定向到登录页,否则返回 403 页面;
委托处理 :实际处理逻辑委托给 AuthenticationEntryPoint(处理认证异常)和 AccessDeniedHandler(处理授权异常)。
关键源码(简化版) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Override public void doFilter (ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { chain.doFilter(request, response); } catch (IOException ex) { throw ex; } catch (Exception ex) { Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase != null ) { handleAuthenticationException(request, response, chain, ase); } else { ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); if (ase != null ) { handleAccessDeniedException(request, response, chain, ase); } else { if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } throw new RuntimeException (ex); } } } }
自定义异常处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override protected void configure (HttpSecurity http) throws Exception { http.exceptionHandling() .authenticationEntryPoint((request, response, ex) -> { response.setContentType("application/json;charset=utf-8" ); response.getWriter().write("{\"code\":401,\"msg\":\"请先登录\"}" ); }) .accessDeniedHandler((request, response, ex) -> { response.setContentType("application/json;charset=utf-8" ); response.getWriter().write("{\"code\":403,\"msg\":\"无访问权限\"}" ); }); }
8. FilterSecurityInterceptor:权限校验的 “裁判” 核心职责 :Spring Security 过滤器链的最后一个过滤器,负责判断当前用户是否有权访问目标资源 ,是权限控制的 “最终裁判”。
工作流程
封装请求 :将 HttpServletRequest、HttpServletResponse、FilterChain 封装为 FilterInvocation;
获取资源权限 :通过 SecurityMetadataSource 获取目标资源(如 /admin/**)所需的权限(如 ROLE_ADMIN);
权限决策 :调用 AccessDecisionManager,对比当前用户的权限(从 SecurityContext 中获取)与资源所需权限;
处理结果:
有权限:放行请求,执行后续过滤器链(最终到达 Controller);
无权限:抛出 AccessDeniedException,由 ExceptionTranslationFilter 处理。
关键源码(核心方法) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation (request, response, chain); invoke(fi); } public void invoke (FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() != null ) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null )) { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); return ; } fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); InterceptorStatusToken token = null ; try { token = super .beforeInvocation(fi); fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super .finallyInvocation(token); } super .afterInvocation(token, null ); }
权限配置示例 1 2 3 4 5 6 7 @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**" ).hasRole("ADMIN" ) .antMatchers("/user/**" ).hasAnyRole("ADMIN" , "USER" ) .anyRequest().authenticated(); }
总结:过滤器链的协作逻辑 Spring Security 过滤器链的核心设计思想是 “单一职责 + 链式协作 ”,每个过滤器专注于一个功能,按顺序协同完成安全控制:
准备阶段 :SecurityContextPersistenceFilter 创建安全上下文,CsrfFilter 处理 CSRF Token;
认证阶段 :LogoutFilter 处理退出,UsernamePasswordAuthenticationFilter 处理登录,AnonymousAuthenticationFilter 分配匿名身份;
管理阶段 :SessionManagementFilter 管理 Session;
校验阶段 :FilterSecurityInterceptor 进行权限校验;
异常处理 :ExceptionTranslationFilter 捕获并处理全流程异常