Spring Security 核心流程全解析:认证与权限控制的底层逻辑
Spring Security 的核心能力分为认证(Authentication) 和权限(Authorization) 两部分,分别对应 “确认用户身份” 和 “判断用户是否有权访问资源”。从 “请求拦截→认证处理→权限校验” 三个维度,逐行拆解流程中的关键组件与数据流转。
认证流程:从登录请求到身份确认
认证流程的核心是 “获取用户凭证→校验凭证→存储认证信息”,入口是 UsernamePasswordAuthenticationFilter(处理表单登录请求),最终通过 AuthenticationManager 和 UserDetailsService 完成校验。
1. 流程入口:UsernamePasswordAuthenticationFilter 拦截登录请求
UsernamePasswordAuthenticationFilter 是处理 POST /login(或自定义登录路径)请求的专用过滤器,继承自 AbstractAuthenticationProcessingFilter,其核心逻辑在父类的 doFilter() 方法中触发。
步骤 1:AbstractAuthenticationProcessingFilter#doFilter—— 过滤器核心调度
所有登录请求首先进入该方法,判断是否需要认证并触发后续逻辑:
1 | public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { |
- 关键判断:
requiresAuthentication()会检查请求路径(如是否是/login)和请求方法(是否是 POST),只有符合条件的请求才会进入认证流程。
步骤 2:UsernamePasswordAuthenticationFilter#attemptAuthentication—— 封装用户凭证
该方法是认证的 “数据准备阶段”,核心是从请求中提取用户名 / 密码,封装成未认证的 Authentication 对象:
1 |
|
UsernamePasswordAuthenticationToken状态:此时构造的对象setAuthenticated(false)(未认证),仅包含用户名和密码,无权限信息。
步骤 3:AuthenticationManager#authenticate—— 委托认证逻辑
AuthenticationManager 是认证的 “调度中心”,默认实现 ProviderManager 不直接处理认证,而是委托给多个 AuthenticationProvider(如处理用户名密码的 DaoAuthenticationProvider、处理 OAuth2 的 OAuth2AuthenticationProvider)。
(1)ProviderManager#authenticate—— 筛选合适的 AuthenticationProvider
1 |
|
supports()判断:DaoAuthenticationProvider仅支持UsernamePasswordAuthenticationToken类型,因此会被选中。
(2)DaoAuthenticationProvider#authenticate—— 实际认证逻辑
DaoAuthenticationProvider 是处理用户名密码认证的核心实现,继承自 AbstractUserDetailsAuthenticationProvider,其 authenticate 方法分为两步:加载用户信息→校验凭证:
1 | // AbstractUserDetailsAuthenticationProvider#authenticate(DaoAuthenticationProvider 继承此方法) |
关键子步骤:
retrieveUser():DaoAuthenticationProvider 的核心方法,调用UserDetailsService从数据库加载用户:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
try {
// 调用自定义 UserDetailsService 加载用户(生产环境需自己实现)
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new UsernameNotFoundException("用户不存在:" + username);
}
return loadedUser;
} catch (UsernameNotFoundException e) {
throw e;
} catch (Exception e) {
throw new AuthenticationServiceException(e.getMessage(), e);
}
}additionalAuthenticationChecks():校验密码,使用PasswordEncoder对比提交的密码(明文)与数据库存储的加密密码:1
2
3
4
5
6
7
8
9
10
11
12
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() == null) {
throw new BadCredentialsException("密码不能为空");
}
// 1. 获取用户提交的密码(明文)
String presentedPassword = authentication.getCredentials().toString();
// 2. 对比加密后的密码(数据库存储的密码 vs 提交密码加密后的值)
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException("密码错误");
}
}createSuccessAuthentication():生成已认证的UsernamePasswordAuthenticationToken,此时setAuthenticated(true),并包含用户权限:1
2
3
4
5
6
7protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
// 生成已认证的 Token,包含用户权限(user.getAuthorities())
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(), user.getAuthorities());
result.setDetails(authentication.getDetails());
return result;
}
步骤 4:认证结果处理 —— 成功与失败
(1)successfulAuthentication()—— 认证成功
认证成功后,核心是将已认证的 Authentication 存入 SecurityContext,确保后续请求能获取用户身份:
1 | protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, |
(2)unsuccessfulAuthentication()—— 认证失败
认证失败后,核心是清理 SecurityContext,避免无效信息残留:
1 | protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, |
权限流程:从请求拦截到权限判断
权限流程的核心是 “检查当前用户是否有权访问目标资源”,入口是 FilterSecurityInterceptor(Spring Security 过滤器链的最后一个过滤器),在请求到达 Controller 前完成权限校验。
1. 核心组件:FilterSecurityInterceptor 的作用
FilterSecurityInterceptor 是 “权限拦截器”,继承自 AbstractSecurityInterceptor,其核心逻辑在 doFilter() 方法中,分为 “权限校验→请求放行→后续处理” 三步:
1 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { |
2. 核心步骤:AbstractSecurityInterceptor#beforeInvocation—— 权限判断
beforeInvocation 是权限校验的核心方法,逻辑可拆解为 “获取资源权限→匹配用户权限→决策是否放行”:
1 | protected InterceptorStatusToken beforeInvocation(Object object) { |
关键子步骤解析:
(1)obtainSecurityMetadataSource()—— 获取资源权限
SecurityMetadataSource 是 “资源权限数据源”,负责根据请求路径(如 /admin/user)返回对应的权限规则(如 ROLE_ADMIN)。Spring Security 提供默认实现 FilterInvocationSecurityMetadataSource,支持基于 URL 模式的权限配置(如你之前配置的 .antMatchers("/admin/**").hasRole("admin"))。
(2)accessDecisionManager.decide()—— 权限决策
AccessDecisionManager 是 “权限决策器”,核心是对比用户的权限与资源所需权限,默认实现 AffirmativeBased(只要有一个权限匹配就放行):
1 | // AffirmativeBased#decide |
RoleVoter投票逻辑:检查用户的GrantedAuthority(如ROLE_ADMIN)是否包含资源所需的权限(如ROLE_ADMIN),包含则投支持票。
3. 权限校验失败处理
若 accessDecisionManager.decide() 抛出 AccessDeniedException,会被 ExceptionTranslationFilter(过滤器链中的前一个过滤器)捕获,处理逻辑如下:
- 若用户未认证:重定向到登录页;
- 若用户已认证但无权限:重定向到 403 无权限页面(如你配置的
.accessDeniedPage("/unauth.html"))。
认证与权限的衔接:SecurityContext 的作用
SecurityContext 是认证与权限流程的 “数据桥梁”,核心作用是存储当前用户的认证信息(Authentication),由 SecurityContextHolder 管理(默认基于 ThreadLocal)。
- 认证流程:成功后将
Authentication存入SecurityContext; - 权限流程:从
SecurityContext中获取Authentication,提取用户权限进行校验。
数据流转总结:
graph LR A[用户提交用户名密码] --> B[UsernamePasswordAuthenticationFilter封装Token] B --> C[DaoAuthenticationProvider调用UserDetailsService加载用户] C --> D[校验密码生成已认证Token] D --> E[存入SecurityContext] E --> F[FilterSecurityInterceptor从SecurityContext获取Token] F --> G[AccessDecisionManager对比权限] G --> H[有权限->放行到Controller] G --> I[无权限->抛出AccessDeniedException]
总结:核心流程与组件
| 流程阶段 | 核心组件 | 核心作用 |
|---|---|---|
| 认证入口 | UsernamePasswordAuthenticationFilter | 拦截登录请求,提取凭证 |
| 认证调度 | AuthenticationManager(ProviderManager) | 委托 AuthenticationProvider 执行认证 |
| 认证实现 | DaoAuthenticationProvider | 调用 UserDetailsService,校验密码 |
| 用户数据 | UserDetailsService | 从数据库加载用户信息 |
| 权限拦截 | FilterSecurityInterceptor | 请求到达前校验权限 |
| 权限决策 | AccessDecisionManager | 对比用户权限与资源权限 |
| 数据存储 | SecurityContextHolder | 管理 SecurityContext,存储认证信息 |
理解这套流程,就能清晰掌握 Spring Security 如何从 “确认用户身份” 到 “控制资源访问”,也能轻松进行自定义扩展(如自定义登录过滤器、自定义权限决策器)
springSecurity流程
认证流程
登录请求进入UsernamePasswordAuthenticationFilter,父类是AbstractAuthenticationProcessingFilter,执行AbstractAuthenticationProcessingFilter的doFilter方法
1
authResult = attemptAuthentication(request, response);
确认该请求是否需要认证,如果需要认证且此时还没有进行认证,则尝试进行认证,调用UsernamePasswordAuthenticationFilter的attemptAuthentication方法,将从请求中获取的用户名和密码封装成UsernamePasswordAuthenticationToken对象(认证状态为未认证)
1
2
3
4
5
6
7
8
9
10UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
// 设置为未认证
setAuthenticated(false);
}通过AuthenticationManager(实现类是ProviderManager)委托AuthenticationProvider(实现类是DaoAuthenticationProvider)关联UserDetailsService进行认证过程
1
2
3
4
5
6
7
8
9
10
11// UsernamePasswordAuthenticationFilter中通过AuthenticationManager进行认证
return this.getAuthenticationManager().authenticate(authRequest);
// ProviderManager中通过AuthenticationProvider进行认证,使用的是DaoAuthenticationProvider对象,DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider类,实际走的是AbstractUserDetailsAuthenticationProvider的authenticate方法
result = provider.authenticate(authentication);
// AbstractUserDetailsAuthenticationProvider的authenticate方法中检索用户,实际执行的是DaoAuthenticationProvider的retrieveUser方法
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
// DaoAuthenticationProvider的retrieveUser方法中通过UserDetailsService就查询用户进行认证,可以自己实现用户查找以及认证过程
loadedUser = this.getUserDetailsService().loadUserByUsername(username);UserDetailsService返回的UserDetails被封装成Authentication
如果认证成功则执行successfulAuthentication
1
successfulAuthentication(request, response, chain, authResult);
如果认证失败则执行unsuccessfulAuthentication
1
unsuccessfulAuthentication(request, response, failed);
权限流程
FilterSecurityInterceptor
判断请求是否有权限
1
InterceptorStatusToken token = super.beforeInvocation(fi);
进行springmvc处理
1
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
v1.3.10