Spring Security 源码分析一:Spring Security 认证过程 - 教程
2026-01-25 08:03 tlnshuju 阅读(0) 评论(0) 收藏 举报1. Spring Security 整体架构概览
Spring Security 的认证过程是一个复杂而精密的流程,涉及多个核心组件的协作。在深入分析之前,我们先了解整体的认证架构:
text
HTTP Request → Security Filter Chain → AuthenticationManager → AuthenticationProvider → UserDetailsService
2. 认证入口:Security Filter Chain
2.1 FilterChainProxy 的初始化
java
// FilterChainProxy 是 Spring Security 的核心过滤器
public class FilterChainProxy extends GenericFilterBean {
private List filterChains;
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 1. 获取当前请求对应的 SecurityFilterChain
List filters = getFilters((HttpServletRequest) request);
if (filters == null || filters.size() == 0) {
// 没有匹配的过滤器链,直接继续原有过滤器链
chain.doFilter(request, response);
return;
}
// 2. 创建 VirtualFilterChain 执行安全过滤器
VirtualFilterChain vfc = new VirtualFilterChain((HttpServletRequest) request,
chain, filters);
vfc.doFilter(request, response);
}
private List getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
}
2.2 默认的安全过滤器链
Spring Security 默认配置了以下过滤器(按顺序执行):
java
// DefaultSecurityFilterChain 包含的标准过滤器顺序
public static class DefaultFilters {
static List> get(WebSecurityConfigurerAdapter adapter) {
List> filters = new ArrayList<>();
// 核心认证相关过滤器
filters.add(WebAsyncManagerIntegrationFilter.class);
filters.add(SecurityContextPersistenceFilter.class); // 安全上下文持久化
filters.add(HeaderWriterFilter.class);
filters.add(CsrfFilter.class);
filters.add(LogoutFilter.class); // 注销处理
// 用户名密码认证过滤器
filters.add(UsernamePasswordAuthenticationFilter.class);
filters.add(DefaultLoginPageGeneratingFilter.class);
filters.add(DefaultLogoutPageGeneratingFilter.class);
filters.add(BasicAuthenticationFilter.class); // HTTP Basic 认证
filters.add(RequestCacheAwareFilter.class);
filters.add(SecurityContextHolderAwareRequestFilter.class);
filters.add(AnonymousAuthenticationFilter.class); // 匿名认证
filters.add(SessionManagementFilter.class);
filters.add(ExceptionTranslationFilter.class); // 异常转换
filters.add(FilterSecurityInterceptor.class); // 授权拦截
return filters;
}
}
3. 认证核心流程详解
3.1 SecurityContextPersistenceFilter - 安全上下文持久化
java
public class SecurityContextPersistenceFilter extends GenericFilterBean {
private SecurityContextRepository repo;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 1. 从 SecurityContextRepository 加载 SecurityContext
SecurityContext contextBeforeChainExecution = repo.loadContext(
new HttpRequestResponseHolder(request, response));
try {
// 2. 设置到 SecurityContextHolder
SecurityContextHolder.setContext(contextBeforeChainExecution);
// 3. 继续执行过滤器链
chain.doFilter(request, response);
} finally {
// 4. 清理 SecurityContextHolder
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
// 5. 保存 SecurityContext 到 Repository
repo.saveContext(contextAfterChainExecution, request, response);
}
}
}
3.2 UsernamePasswordAuthenticationFilter - 表单认证处理
java
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
// 认证处理的核心方法
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 1. 验证请求方法必须是 POST
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 2. 从请求参数中提取用户名和密码
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 3. 创建认证令牌
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
// 4. 设置详细信息
setDetails(request, authRequest);
// 5. 委托给 AuthenticationManager 进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
}
3.3 AuthenticationManager 认证管理器
ProviderManager 实现
java
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
private List providers;
private AuthenticationManager parent;
private AuthenticationEventPublisher eventPublisher;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
// 1. 遍历所有 AuthenticationProvider 尝试认证
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
// 2. 调用具体 Provider 进行认证
result = provider.authenticate(authentication);
if (result != null) {
// 3. 认证成功,复制详细信息
copyDetails(authentication, result);
break;
}
} catch (AccountStatusException | InternalAuthenticationServiceException e) {
// 4. 处理特定异常
prepareException(e, authentication);
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
// 5. 如果没有任何 Provider 认证成功
if (result == null && parent != null) {
try {
// 6. 尝试父级 AuthenticationManager
result = parent.authenticate(authentication);
} catch (ProviderNotFoundException e) {
// 忽略,继续处理
} catch (AuthenticationException e) {
lastException = e;
}
}
if (result != null) {
// 7. 认证成功,发布事件
if (eventPublisher != null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// 8. 认证失败处理
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
}
3.4 DaoAuthenticationProvider - 基于数据库的认证提供者
java
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private PasswordEncoder passwordEncoder;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
// 主要的认证逻辑
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 1. 密码验证
if (authentication.getCredentials() == null) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
// 2. 获取提交的密码
String presentedPassword = authentication.getCredentials().toString();
// 3. 使用 PasswordEncoder 验证密码
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
// 检索用户信息
@Override
protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
try {
// 1. 通过 UserDetailsService 加载用户
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
} catch (UsernameNotFoundException ex) {
// 2. 用户不存在异常处理
mitigateAgainstTimingAttack(authentication);
throw ex;
} catch (InternalAuthenticationServiceException ex) {
throw ex;
} catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
// 防止时序攻击
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
// 执行密码编码操作,消耗与正常认证相同的时间
this.passwordEncoder.matches(presentedPassword, TIMING_ATTACK_PROTECTION);
}
}
}
3.5 UserDetailsService 用户详情服务
java
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
// 内存实现示例
public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
private final Map users = new HashMap<>();
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MutableUserDetails user = this.users.get(username.toLowerCase());
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new User(user.getUsername(), user.getPassword(), user.isEnabled(),
user.isAccountNonExpired(), user.isCredentialsNonExpired(),
user.isAccountNonLocked(), user.getAuthorities());
}
}
// 数据库实现示例
@Service
public class JdbcUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 从数据库查询用户
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
// 2. 查询用户权限
List authorities = authorityRepository.findByUserId(user.getId())
.stream()
.map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
.collect(Collectors.toList());
// 3. 构建 UserDetails 对象
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(authorities)
.accountExpired(user.isAccountExpired())
.accountLocked(user.isAccountLocked())
.credentialsExpired(user.isCredentialsExpired())
.disabled(!user.isEnabled())
.build();
}
}
4. 密码编码与验证
4.1 PasswordEncoder 接口与实现
java
public interface PasswordEncoder {
// 编码原始密码
String encode(CharSequence rawPassword);
// 验证密码是否匹配
boolean matches(CharSequence rawPassword, String encodedPassword);
// 检查编码是否需要升级
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
// BCrypt 实现
public class BCryptPasswordEncoder implements PasswordEncoder {
private final BCryptPasswordEncoder.BCryptVersion version;
private final int strength;
private final SecureRandom random;
@Override
public String encode(CharSequence rawPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
String salt = getSalt();
return BCrypt.hashpw(rawPassword.toString(), salt);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
if (encodedPassword == null || encodedPassword.length() == 0) {
logger.warn("Empty encoded password");
return false;
}
if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
logger.warn("Encoded password does not look like BCrypt");
return false;
}
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
}
private String getSalt() {
if (this.random != null) {
return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);
}
return BCrypt.gensalt(this.version.getVersion(), this.strength);
}
}
4.2 DelegatingPasswordEncoder 委托密码编码器
java
public class DelegatingPasswordEncoder implements PasswordEncoder {
private static final String PREFIX = "{";
private static final String SUFFIX = "}";
private final String idForEncode;
private final PasswordEncoder passwordEncoderForEncode;
private final Map idToPasswordEncoder;
@Override
public String encode(CharSequence rawPassword) {
// 添加编码器ID前缀
return PREFIX + this.idForEncode + SUFFIX +
this.passwordEncoderForEncode.encode(rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
if (rawPassword == null && prefixEncodedPassword == null) {
return true;
}
// 1. 提取编码器ID
String id = extractId(prefixEncodedPassword);
// 2. 获取对应的密码编码器
PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
if (delegate == null) {
throw new IllegalArgumentException("There is no PasswordEncoder mapped for id \"" + id + "\"");
}
// 3. 提取编码后的密码(不含前缀)
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
// 4. 使用对应的编码器验证密码
return delegate.matches(rawPassword, encodedPassword);
}
private String extractId(String prefixEncodedPassword) {
if (prefixEncodedPassword == null) {
return null;
}
int start = prefixEncodedPassword.indexOf(PREFIX);
if (start != 0) {
return null;
}
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
if (end < 0) {
return null;
}
return prefixEncodedPassword.substring(start + 1, end);
}
}
5. 认证成功与失败处理
5.1 认证成功处理
java
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
// 1. 将认证信息设置到 SecurityContext
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
SecurityContextHolder.setContext(context);
// 2. 发布认证成功事件
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
// 3. 调用成功处理器
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
}
// 默认的成功处理器
public class SavedRequestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
// 1. 获取保存的请求
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest == null) {
// 2. 没有保存的请求,使用默认目标页
super.onAuthenticationSuccess(request, response, authentication);
return;
}
String targetUrlParameter = getTargetUrlParameter();
if (isAlwaysUseDefaultTargetUrl() ||
(targetUrlParameter != null &&
StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
// 3. 明确指定了目标页或总是使用默认页
requestCache.removeRequest(request, response);
super.onAuthenticationSuccess(request, response, authentication);
return;
}
// 4. 清除保存的请求
clearAuthenticationAttributes(request);
// 5. 重定向到原始请求页面
String targetUrl = savedRequest.getRedirectUrl();
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
}
5.2 认证失败处理
java
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean {
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
// 1. 清除 SecurityContext
SecurityContextHolder.clearContext();
// 2. 发布认证失败事件
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new AuthenticationFailureBadCredentialsEvent(
authentication, failed));
}
// 3. 记住我服务处理
rememberMeServices.loginFail(request, response);
// 4. 调用失败处理器
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
}
// 默认的失败处理器
public class SimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String defaultFailureUrl;
private boolean forwardToDestination = false;
private boolean allowSessionCreation = true;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
if (defaultFailureUrl == null) {
logger.debug("No failure URL set, sending 401 Unauthorized error");
response.sendError(HttpStatus.UNAUTHORIZED.value(),
HttpStatus.UNAUTHORIZED.getReasonPhrase());
return;
}
// 保存异常信息到 session
saveException(request, exception);
if (forwardToDestination) {
logger.debug("Forwarding to " + defaultFailureUrl);
request.getRequestDispatcher(defaultFailureUrl).forward(request, response);
} else {
redirectStrategy.sendRedirect(request, response, defaultFailureUrl);
}
}
}
6. SecurityContext 安全上下文管理
6.1 SecurityContext 接口与实现
java
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
// 默认实现
public class SecurityContextImpl implements SecurityContext {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private Authentication authentication;
@Override
public Authentication getAuthentication() {
return authentication;
}
@Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
}
// 安全上下文持有器
public class SecurityContextHolder {
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";
private static SecurityContextHolderStrategy strategy;
private static int initializeCount = 0;
static {
initialize();
}
public static void clearContext() {
strategy.clearContext();
}
public static SecurityContext getContext() {
return strategy.getContext();
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
private static void initialize() {
// 初始化策略
String strategyName = System.getProperty(SYSTEM_PROPERTY);
if (strategyName == null) {
strategyName = MODE_THREADLOCAL;
}
initializeStrategy(strategyName);
initializeCount++;
}
}
6.2 SecurityContextRepository 安全上下文存储
java
public interface SecurityContextRepository {
SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
void saveContext(SecurityContext context, HttpServletRequest request,
HttpServletResponse response);
boolean containsContext(HttpServletRequest request);
}
// 基于 HttpSession 的实现
public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
@Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) {
context = generateNewContext();
}
return context;
}
private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
if (httpSession == null) {
return null;
}
Object contextFromSession = httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY);
if (contextFromSession == null) {
return null;
}
if (!(contextFromSession instanceof SecurityContext)) {
return null;
}
return (SecurityContext) contextFromSession;
}
@Override
public void saveContext(SecurityContext context, HttpServletRequest request,
HttpServletResponse response) {
HttpSession httpSession = request.getSession(false);
if (httpSession == null) {
// 没有 Session 且认证为 null,不需要保存
if (context.getAuthentication() == null) {
return;
}
// 创建新的 Session
httpSession = request.getSession(true);
}
httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
}
}
7. 匿名认证处理
7.1 AnonymousAuthenticationFilter
java
public class AnonymousAuthenticationFilter extends GenericFilterBean
implements InitializingBean {
private AuthenticationDetailsSource authenticationDetailsSource;
private String key;
private Object principal;
private List authorities;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// 1. 检查是否已有认证信息
if (SecurityContextHolder.getContext().getAuthentication() == null) {
// 2. 创建匿名认证令牌
Authentication authentication = createAuthentication((HttpServletRequest) req);
// 3. 设置到安全上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(req, res);
}
protected Authentication createAuthentication(HttpServletRequest request) {
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(key,
principal, authorities);
token.setDetails(authenticationDetailsSource.buildDetails(request));
return token;
}
}
8. 异常处理与转换
8.1 ExceptionTranslationFilter
java
public class ExceptionTranslationFilter extends GenericFilterBean {
private AuthenticationEntryPoint authenticationEntryPoint;
private AccessDeniedHandler accessDeniedHandler;
private final AuthenticationTrustResolver authenticationTrustResolver;
private final ThrowableAnalyzer throwableAnalyzer;
@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 (Exception ex) {
// 处理过滤器链中抛出的异常
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
// 1. 处理认证异常
RuntimeException ase = (AuthenticationException)
throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase != null) {
handleAuthenticationException(request, response, chain, (AuthenticationException) ase);
return;
}
// 2. 处理访问拒绝异常
ase = (AccessDeniedException)
throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
if (ase != null) {
handleAccessDeniedException(request, response, chain, (AccessDeniedException) ase);
return;
}
}
}
private void handleAuthenticationException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, AuthenticationException failed)
throws IOException, ServletException {
// 1. 清除 SecurityContext
SecurityContextHolder.clearContext();
// 2. 记录日志
logger.debug("Authentication exception occurred; redirecting to authentication entry point", failed);
// 3. 调用认证入口点
authenticationEntryPoint.commence(request, response, failed);
}
private void handleAccessDeniedException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, AccessDeniedException denied)
throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 1. 检查是否是匿名用户或记住我用户
if (authenticationTrustResolver.isAnonymous(authentication) ||
authenticationTrustResolver.isRememberMe(authentication)) {
logger.debug("Access is denied (user is " +
(authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") +
"); redirecting to authentication entry point", denied);
// 2. 匿名或记住我用户,重定向到登录页
authenticationEntryPoint.commence(request, response,
new InsufficientAuthenticationException("Full authentication is required to access this resource"));
} else {
logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", denied);
// 3. 认证用户但权限不足,调用访问拒绝处理器
accessDeniedHandler.handle(request, response, denied);
}
}
}
9. 完整的认证流程总结
通过以上源码分析,我们可以总结出 Spring Security 认证的完整流程:
请求拦截:SecurityFilterChain 拦截 HTTP 请求
上下文加载:SecurityContextPersistenceFilter 从 Session 加载 SecurityContext
认证处理:UsernamePasswordAuthenticationFilter 处理表单登录
认证管理:AuthenticationManager 委托给合适的 AuthenticationProvider
用户加载:DaoAuthenticationProvider 通过 UserDetailsService 加载用户信息
密码验证:PasswordEncoder 验证密码正确性
认证结果:认证成功创建 Authentication 对象,失败抛出异常
上下文保存:认证信息保存到 SecurityContext 和 Session
后续处理:根据认证结果进行成功或失败的重定向/响应
浙公网安备 33010602011771号