springSecurity提供的过滤器
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。
- 若
关键源码
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 {
// 1. 从 Session 中获取或创建 SecurityContext
SecurityContext contextBeforeChainExecution = repo.loadContext(new HttpRequestResponseHolder(request, response));
// 2. 存入 SecurityContextHolder(ThreadLocal)
SecurityContextHolder.setContext(contextBeforeChainExecution);
// 3. 执行后续过滤器链
chain.doFilter(request, response);
} finally {
// 4. 请求结束:清空当前线程的 SecurityContext
SecurityContextHolder.clearContext();
// 5. 保存 SecurityContext 到 Session(若有变化)
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)。
- 前端提交非安全请求(POST、PUT、DELETE 等)时,需在请求头(如
关键源码(简化版)
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
// 1. 从请求中提取 CSRF Token
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
boolean missingToken = (csrfToken == null);
// 2. 若没有 Token,生成新 Token 并保存到 Session
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
// 3. 判断请求是否需要 CSRF 验证(非 GET/HEAD/OPTIONS/TRACE 请求需要)
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
// 4. 从请求中提取用户提交的 Token
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
// 5. 对比 Token:不一致则抛出异常
if (!csrfToken.getToken().equals(actualToken)) {
throw new InvalidCsrfTokenException(csrfToken, actualToken);
}
// 6. Token 验证通过,执行后续过滤器
filterChain.doFilter(request, response);
}
常见场景与配置
前后端分离项目:通常关闭 CSRF 防护(前端通过 Token 认证,无 Session),配置
http.csrf().disable();传统表单项目:需在表单中添加隐藏域携带 CSRF Token:
<form method="post" action="/user/update"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"> <!-- 其他表单字段 --> </form>
3. LogoutFilter:处理退出登录的 “执行者”
核心职责:拦截退出登录请求(默认 /logout),执行退出逻辑(销毁 Session、清空 SecurityContext、删除 “记住我” Cookie 等)。
工作流程
- 拦截请求:匹配退出登录路径(默认
/logout,可通过http.logout().logoutUrl("/custom-logout")自定义); - 执行退出逻辑:
- 调用
LogoutHandler链(如SecurityContextLogoutHandler清空SecurityContext、CookieClearingLogoutHandler删除 Cookie); - 触发
LogoutSuccessHandler(默认重定向到登录页,可自定义为返回 JSON 结果);
- 调用
- 重定向或返回结果:退出成功后,默认重定向到
/login?logout,可通过logoutSuccessUrl("/index")自定义。
关键配置示例
protected void configure(HttpSecurity http) throws Exception {
http.logout()
.logoutUrl("/user/logout") // 自定义退出路径
.logoutSuccessUrl("/login?success") // 退出成功跳转路径
.invalidateHttpSession(true) // 销毁当前 Session
.deleteCookies("JSESSIONID", "remember-me") // 删除指定 Cookie
.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,重定向到登录页并显示错误。
- 认证成功:调用
关键源码(核心方法)
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 1. 仅允许 POST 方法
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("不支持的认证方法:" + request.getMethod());
}
// 2. 提取用户名和密码
String username = obtainUsername(request);
String password = obtainPassword(request);
username = (username != null) ? username.trim() : "";
password = (password != null) ? password : "";
// 3. 封装未认证的 Token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
// 4. 委托 AuthenticationManager 认证
return this.getAuthenticationManager().authenticate(authRequest);
}
自定义配置示例
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") // 自定义登录页面
.loginProcessingUrl("/user/login") // 自定义登录请求路径
.usernameParameter("userName") // 自定义用户名参数名(默认 username)
.passwordParameter("passWord") // 自定义密码参数名(默认 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)。
关键源码(简化版)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 1. 若已认证(有 Authentication),直接放行
if (SecurityContextHolder.getContext().getAuthentication() == null) {
// 2. 创建匿名身份 Token(默认角色 ROLE_ANONYMOUS)
Authentication anonymousAuth = new AnonymousAuthenticationToken(
key, anonymousUser, authorities);
// 3. 存入 SecurityContext
SecurityContextHolder.getContext().setAuthentication(anonymousAuth);
}
// 4. 执行后续过滤器
chain.doFilter(request, response);
}
匿名用户的权限控制
可通过 permitAll() 允许匿名用户访问特定路径,或通过 anonymous() 明确指定:
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 通过以下方式防御:
登录时更换 Session ID:用户登录成功后,生成新的 Session ID,废弃旧 ID(默认开启);
配置示例:
http.sessionManagement() .sessionFixation().migrateSession(); // 登录时迁移 Session(更换 Session ID)
核心功能:限制会话数量
http.sessionManagement()
.maximumSessions(1) // 同一用户最多允许 1 个会话
.expiredUrl("/login?expired") // 会话过期后跳转路径
.maxSessionsPreventsLogin(true); // 达到最大会话数时,禁止新登录(false 表示踢掉旧会话)
7. ExceptionTranslationFilter:异常的 “翻译官”
核心职责:捕获并处理认证 / 授权过程中抛出的异常(如 AuthenticationException、AccessDeniedException),将异常 “翻译” 为用户可理解的响应(如重定向到登录页、返回 403 页面),是安全流程的 “异常处理中心”。
工作流程
- 捕获异常:通过
try-catch捕获后续过滤器链抛出的异常; - 分类处理:
- 认证异常(AuthenticationException):如用户名密码错误、用户不存在,重定向到登录页;
- 授权异常(AccessDeniedException):如用户已登录但无权限,若用户是匿名用户则重定向到登录页,否则返回 403 页面;
- 委托处理:实际处理逻辑委托给
AuthenticationEntryPoint(处理认证异常)和AccessDeniedHandler(处理授权异常)。
关键源码(简化版)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
// 1. 执行后续过滤器链,捕获异常
chain.doFilter(request, response);
} catch (IOException ex) {
throw ex;
} catch (Exception ex) {
// 2. 提取 Throwable 中的 AuthenticationException 或 AccessDeniedException
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase != null) {
// 3. 处理认证异常(如重定向到登录页)
handleAuthenticationException(request, response, chain, ase);
} else {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
if (ase != null) {
// 4. 处理授权异常(如返回 403 页面)
handleAccessDeniedException(request, response, chain, ase);
} else {
// 5. 其他异常,直接抛出
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new RuntimeException(ex);
}
}
}
}
自定义异常处理
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint((request, response, ex) -> {
// 自定义认证异常处理(如返回 JSON 结果)
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\":401,\"msg\":\"请先登录\"}");
})
.accessDeniedHandler((request, response, ex) -> {
// 自定义授权异常处理(如返回 403 JSON)
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处理。
关键源码(核心方法)
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 {
// 1. 检查是否已处理过该请求(防止重复校验)
if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}
// 2. 标记请求已处理
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
InterceptorStatusToken token = null;
try {
// 3. 核心:权限校验(委托给父类 AbstractSecurityInterceptor)
token = super.beforeInvocation(fi);
// 4. 权限校验通过,放行请求
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
// 5. 清理权限校验状态
super.finallyInvocation(token);
}
// 6. 权限校验后的处理(如触发事件)
super.afterInvocation(token, null);
}
权限配置示例
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // /admin/** 需 ROLE_ADMIN 角色
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER") // /user/** 需 ADMIN 或 USER 角色
.anyRequest().authenticated(); // 其他请求需认证
}
总结:过滤器链的协作逻辑
Spring Security 过滤器链的核心设计思想是 “单一职责 + 链式协作”,每个过滤器专注于一个功能,按顺序协同完成安全控制:
- 准备阶段:
SecurityContextPersistenceFilter创建安全上下文,CsrfFilter处理 CSRF Token; - 认证阶段:
LogoutFilter处理退出,UsernamePasswordAuthenticationFilter处理登录,AnonymousAuthenticationFilter分配匿名身份; - 管理阶段:
SessionManagementFilter管理 Session; - 校验阶段:
FilterSecurityInterceptor进行权限校验; - 异常处理:
ExceptionTranslationFilter捕获并处理全流程异常