最新Spring Security实战教程(三):Spring Security的底层原理解析

1. 前言

相信通过前面两个章节的讲解,大家已经对Spring Security有了一个初步认识。今天这个章节我们主要聊一聊Spring Security的底层原理。为什么我们只要简单的一个配置就可以实现想要的功能?实际上Spring SecurityServlet支持就是基于Servlet过滤器Filter

官方文档参考:https://docs.spring.io/spring-security/reference/servlet/architecture.html

回顾过滤器Filter

以下引入官方文档的示例图来回顾一下单个HTTP请求的处理程序的典型分层:

过滤器分层架构

开发者可以在每个Filter过滤器中对请求进行修改或增强。

委托过滤代理DelegatingFilterProxy

Spring提供了一个名为DelegatingFilterProxy的Filter实现,它可以在Servlet容器Spring容器之间建立桥梁:

DelegatingFilterProxy工作原理

为什么需要一个桥梁?因为Servlet容器并不知道Spring容器中定义的Bean,那么就需要一个代理,帮助我们将Servlet容器中的Filter实例委托给Spring容器管理。观察下面的代码原理:

1
2
3
4
5
6
7
8
9
10
11
12
// 原始Servlet Filter
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
chain.doFilter(request, response);
}

// 模拟DelegatingFilterProxy伪代码
// 1、获取已注册为Spring Bean的Filter
// 2、将工作委托给Spring Bean
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
Filter delegate = getFilterBean(someBeanName);
delegate.doFilter(request, response);
}

过滤链代理FilterChainProxy

Spring Security的Servlet支持包含在FilterChainProxy中。FilterChainProxySpring Security提供的一个特殊的Filter,它允许通过Filter委托给许多SecurityFilterChain实例:

FilterChainProxy架构

安全过滤链SecurityFilterChain

SecurityFilterChainFilterChainProxy用来确定应该为当前请求调用哪些Spring Security Filter实例列表:

SecurityFilterChain结构

下图显示了多个SecurityFilterChain实例:

多SecurityFilterChain示意图

在上图中,由FilterChainProxy决定应该使用哪个SecurityFilterChain仅调用第一个匹配的SecurityFilterChain):

  • 如果请求的URL为/api/messages/,则它首先匹配SecurityFilterChain0的/api/**模式
  • 如果请求的URL为/messages/,则它与SecurityFilterChainn匹配
  • 依次查找匹配…

从上面图解中,我们可以总结Spring Security为开发者提供了一整套基于过滤器链(Filter Chain)的安全防护机制。从用户发起HTTP请求,到经过多个安全过滤器的逐层检查,直至最终认证与授权完成。

我们来看一下Spring Security默认DefaultSecurityFilterChain启动时默认加载的16个过滤器:

默认过滤器链

Spring Security已经帮我们封装了大部分的过滤器,实际上我们主要关注以下两个方面即可:

  • 身份认证流程:如何通过表单登录(或其他方式)实现用户身份验证,及其内部如何利用AuthenticationManagerProviderManager和多个AuthenticationProvider协同工作。
  • 授权机制解析:如何通过安全拦截器(如AbstractSecurityInterceptor)实现方法级别和URL级别的权限控制。

2. 核心架构

Spring Security中,整个安全流程围绕着一个核心组件展开:FilterChainProxy。它负责拦截所有HTTP请求,并将其传递给预先配置好的多个安全过滤器。这些过滤器中,最关键的包括:

  • UsernamePasswordAuthenticationFilter:处理基于表单提交的登录请求。
  • BasicAuthenticationFilter:处理HTTP Basic认证请求。
  • FilterSecurityInterceptor:负责权限授权,调用AccessDecisionManager来判定当前用户是否有权访问目标资源。

下面是一个简单的架构示意图,帮助你直观了解整个流程:

核心架构示意图

图解说明
当请求到达FilterChainProxy时,根据请求类型,系统会交由不同的过滤器处理:

  • 认证流程:例如表单登录时,由UsernamePasswordAuthenticationFilter拦截,调用AuthenticationManager进行身份校验。
  • 授权流程FilterSecurityInterceptor负责拦截已认证用户的请求,并通过AccessDecisionManager判断其权限是否足够。

3. 身份认证流程详解

❶ 请求拦截与过滤器链

所有HTTP请求首先进入FilterChainProxy,该类遍历预先配置好的过滤器链。以表单登录为例,UsernamePasswordAuthenticationFilter会在匹配到指定URL后触发认证逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

// 构造时指定处理的URL,如 "/perform_login"
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/perform_login", "POST"));
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
// 从请求中提取用户名和密码
String username = obtainUsername(request);
String password = obtainPassword(request);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

// 调用 AuthenticationManager 进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
}

说明
这里的attemptAuthentication方法负责解析用户提交的数据,然后构造一个UsernamePasswordAuthenticationToken对象,并交由AuthenticationManager处理。

❷ AuthenticationManager与ProviderManager

AuthenticationManager是一个接口,其最常用的实现是ProviderManagerProviderManager持有一系列AuthenticationProvider,用于逐个尝试认证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ProviderManager implements AuthenticationManager {

private List<AuthenticationProvider> providers;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 遍历所有 AuthenticationProvider
for (AuthenticationProvider provider : providers) {
if (provider.supports(authentication.getClass())) {
Authentication result = provider.authenticate(authentication);
if (result != null) {
// 认证成功后返回已认证的Authentication对象
return result;
}
}
}
throw new ProviderNotFoundException("无法找到对应的AuthenticationProvider");
}
}

说明
ProviderManager会遍历所有注册的AuthenticationProvider(例如DaoAuthenticationProviderLdapAuthenticationProvider等),直到某个Provider成功认证为止。如果没有Provider能够处理,认证将失败并抛出异常。

❸ 认证提供者(AuthenticationProvider)

一个典型的AuthenticationProvider(如DaoAuthenticationProvider)主要完成以下工作:

  • 通过UserDetailsService加载用户信息;
  • 利用PasswordEncoder校验密码;
  • 如果认证成功,构造一个包含用户权限信息的已认证Authentication对象。

下面是一个自定义AuthenticationProvider的简单示例:

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
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

@Autowired
private MyUserDetailsService userDetailsService;

@Autowired
private PasswordEncoder passwordEncoder;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String rawPassword = (String) authentication.getCredentials();

UserDetails user = userDetailsService.loadUserByUsername(username);
if (user == null) {
throw new BadCredentialsException("用户不存在");
}

if (!passwordEncoder.matches(rawPassword, user.getPassword())) {
throw new BadCredentialsException("密码错误");
}

return new UsernamePasswordAuthenticationToken(username, rawPassword, user.getAuthorities());
}

@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}

源码剖析
authenticate方法中,我们可以看到Spring Security如何利用UserDetailsService与PasswordEncoder协同工作,确保只有正确的凭证才能获得认证成功的结果。


4. 授权机制解析

认证成功后,系统进入授权阶段。授权主要通过FilterSecurityInterceptor完成,该拦截器负责在请求被具体业务逻辑处理前检查用户是否有权限访问对应资源。

❶ FilterSecurityInterceptor工作原理

FilterSecurityInterceptor继承自AbstractSecurityInterceptor,其主要职责为:

  • 获取当前请求对应的安全配置(如所需角色、权限等),通常由SecurityMetadataSource提供;
  • 调用AccessDecisionManager,依据用户权限集合决定是否允许访问;
  • 若访问被拒绝,则抛出AccessDeniedException。

以下代码展示了大致流程:

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
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

@Autowired
private SecurityMetadataSource securityMetadataSource;

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 封装请求信息
FilterInvocation fi = new FilterInvocation(request, response, chain);
// 获取安全配置(资源需要的权限)
Collection<ConfigAttribute> attributes = securityMetadataSource.getAttributes(fi);

// 若资源无安全配置,则直接放行
if (attributes == null || attributes.isEmpty()) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
return;
}

// 检查用户是否具有访问权限
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
}

图解说明

授权流程示意图

通过这种设计,Spring Security能够灵活配置URL、方法等不同层次的授权策略。

❷ AccessDecisionManager与决策流程

AccessDecisionManager根据传入的用户权限集合、请求所需权限及上下文信息进行判断。常见实现包括AffirmativeBased、ConsensusBased和UnanimousBased。

伪代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AffirmativeBased implements AccessDecisionManager {

@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException {
for (ConfigAttribute attribute : configAttributes) {
if (this.supports(attribute)) {
// 如果任何一个决策者同意访问,则允许
if (voter.vote(authentication, object, configAttributes) == AccessDecisionVoter.ACCESS_GRANTED) {
return;
}
}
}
throw new AccessDeniedException("没有足够权限访问该资源");
}
}

说明
这种投票机制允许开发者根据实际业务需求定制多重决策规则,保证授权过程既灵活又安全。


5. 自定义扩展点与源码调试

Spring Security的设计充分考虑了扩展性。常见的扩展点包括:

  • 自定义Filter:在现有FilterChain中插入新的安全过滤器;
  • 自定义AuthenticationProvider:实现特定业务场景下的身份验证;
  • 自定义AccessDecisionManager:满足细粒度的授权需求;
  • 扩展SecurityMetadataSource:支持基于动态资源的权限配置。

在IDEA调试源码时,双击Shift弹出源码搜索框,你可以通过断点跟踪FilterChainProxyAuthenticationManagerProviderManager的调用过程,进一步了解每个组件的具体职责与协作方式。


6. 总结

通过本文的源码剖析,我们了解了Spring Security的核心架构与工作流程:

  • 认证流程从过滤器链的拦截,到UsernamePasswordAuthenticationFilter的处理,再到AuthenticationManager与多个AuthenticationProvider的协作。
  • 授权流程则通过FilterSecurityInterceptor获取安全配置,并调用AccessDecisionManager决策用户是否具有访问权限。

这些机制保证了Spring Security在面对复杂安全场景时依然具有高度的灵活性和扩展性。深入理解这些原理,有助于你在实际项目中更精准地定制安全策略,并针对性地进行功能扩展或问题排查。