代码改变世界

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 认证的完整流程:

  1. 请求拦截:SecurityFilterChain 拦截 HTTP 请求

  2. 上下文加载:SecurityContextPersistenceFilter 从 Session 加载 SecurityContext

  3. 认证处理:UsernamePasswordAuthenticationFilter 处理表单登录

  4. 认证管理:AuthenticationManager 委托给合适的 AuthenticationProvider

  5. 用户加载:DaoAuthenticationProvider 通过 UserDetailsService 加载用户信息

  6. 密码验证:PasswordEncoder 验证密码正确性

  7. 认证结果:认证成功创建 Authentication 对象,失败抛出异常

  8. 上下文保存:认证信息保存到 SecurityContext 和 Session

  9. 后续处理:根据认证结果进行成功或失败的重定向/响应