Spring Security 教程(25)、架构和实现

FilterChainProxy:请求的安全入口

architecture-implementation 配图
architecture-implementation 配图
architecture-implementation 配图

FilterChainProxy 是 Spring Security 的核心 Filter,它拦截所有 HTTP 请求,将它们路由到合适的 SecurityFilterChain。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// FilterChainProxy 内部逻辑简化
public void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
List<SecurityFilterChain> chains = getFilterChains();

for (SecurityFilterChain chain : chains) {
if (chain.matches(request)) {
chain.doFilter(request, response, chain);
return;
}
}

// 没有匹配的链,直接放行
chain.doFilter(request, response);
}

SecurityFilterChain 的构成

每个 SecurityFilterChain 由多个 Filter 组成:

1
2
3
4
5
6
7
8
9
10
11
12
SecurityFilterChain
├── SecurityContextPersistenceFilter # 管理 SecurityContext
├── LogoutFilter # 处理登出
├── UsernamePasswordAuthenticationFilter # 处理表单登录
├── BasicAuthenticationFilter # 处理 Basic 认证
├── RequestCacheAwareFilter # 请求缓存
├── SecurityContextHolderAwareRequestFilter # 请求包装
├── RememberMeAuthenticationFilter # Remember-Me 认证
├── AnonymousAuthenticationFilter # 匿名认证
├── SessionManagementFilter # 会话管理
├── ExceptionTranslationFilter # 异常处理
└── FilterSecurityInterceptor # 最终授权检查

OncePerRequestFilter:每个请求只执行一次

OncePerRequestFilter 是 FilterChainProxy 中大多数 Filter 的基类。它确保每个 Filter 在每个请求中只执行一次,防止重复执行导致的性能问题和状态混乱。

SecurityContextPersistenceFilter:上下文管理

这个 Filter 在每个请求开始前从 SecurityRepository 加载 SecurityContext,请求结束后保存。

1
2
3
4
5
6
7
8
// 请求开始前
SecurityContext ctx = securityContextRepository.loadContext(holder);
SecurityContextHolder.setContext(ctx);

// 请求结束后
securityContextRepository.saveContext(ctx, holder,
afterRequestInProgress);
SecurityContextHolder.clearContext();

默认使用 HttpSessionSecurityContextRepository,将 SecurityContext 存储在 HTTP Session 中。

ExceptionTranslationFilter:异常处理

这个 Filter 捕获安全相关的异常,转换为合适的 HTTP 响应:

异常 处理方式
AuthenticationException 重定向到登录页或返回 401
AccessDeniedException 返回 403 或重定向到拒绝页面
1
2
3
4
5
catch (AuthenticationException authException) {
authenticationEntryPoint.commence(request, response, authException);
} catch (AccessDeniedException accessDeniedException) {
AccessDeniedHandler.handle(request, response, accessDeniedException);
}