认证源码分析与自定义后端认证逻辑

简介: 本文深入分析Spring Security认证流程,从UsernamePasswordAuthenticationFilter入手,解析用户认证请求的处理过程,逐步揭示AuthenticationManager、AuthenticationProvider及UserDetailsService等核心组件的协作机制,帮助理解如何基于源码实现自定义认证逻辑。

1.认证流程分析

UsernamePasswordAuthenticationFilter

先看主要负责认证的过滤器UsernamePasswordAuthenticationFilter,有删减,注意注释。

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 = "username";
    private String passwordParameter = "password";
    private boolean postOnly = true;
    
    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }
    
    public Authentication attemptAuthentication(HttpServletRequest request, 
                                                HttpServletResponse response) throws AuthenticationException {
        //必须为POST请求
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " +
                                                     request.getMethod());
        } else {
            
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            
            if (username == null) {
                username = "";
            }
            
            if (password == null) {
                password = "";
            }
            
            username = username.trim();
            
            //将填写的用户名和密码封装到了UsernamePasswordAuthenticationToken中
            UsernamePasswordAuthenticationToken authRequest = new
            UsernamePasswordAuthenticationToken(username, password);
            
            this.setDetails(request, authRequest);
            //调用AuthenticationManager对象实现认证
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
}

AuthenticationManager

由上面源码得知,真正认证操作在AuthenticationManager里面!

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
    
    private static final Log logger = LogFactory.getLog(ProviderManager.class);
    private AuthenticationEventPublisher eventPublisher;
    private List<AuthenticationProvider> providers;
    protected MessageSourceAccessor messages;
    private AuthenticationManager parent;
    private boolean eraseCredentialsAfterAuthentication;
    
    //注意AuthenticationProvider这个对象,SpringSecurity针对每一种认证,什么qq登录啊,
    //用户名密码登陆啊,微信登录啊都封装了一个AuthenticationProvider对象。
    public ProviderManager(List<AuthenticationProvider> providers) {
        this(providers, (AuthenticationManager)null);
    }
    
    public Authentication authenticate(Authentication authentication) throws
    AuthenticationException {
        
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
        boolean debug = logger.isDebugEnabled();
        Iterator var8 = this.getProviders().iterator();
        
        //循环所有AuthenticationProvider,匹配当前认证类型。
        while(var8.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var8.next();
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " +
                                 provider.getClass().getName());
                }
                try {
                    //找到了对应认证类型就继续调用AuthenticationProvider对象完成认证业务。
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException var13) {
                    this.prepareException(var13, authentication);
                    throw var13;
                } catch (InternalAuthenticationServiceException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }
        
        if (result == null && this.parent != null) {
            try {
                result = parentResult = this.parent.authenticate(authentication);
            } catch (ProviderNotFoundException var11) {
            } catch (AuthenticationException var12) {
                parentException = var12;
                lastException = var12;
            }
        }
        
        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof
                CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }
            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }
            return result;
        } else {
            if (lastException == null) {
                lastException = new
                ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new
                                                                   Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }
            if (parentException == null) {
                this.prepareException((AuthenticationException)lastException, authentication);
            }
            throw lastException;
        }
    }
}

AbstractUserDetailsAuthenticationProvider

咱们继续再找到AuthenticationProvider的实现类AbstractUserDetailsAuthenticationProvider:

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
    private PasswordEncoder passwordEncoder;
    private volatile String userNotFoundEncodedPassword;
    private UserDetailsService userDetailsService;
    private UserDetailsPasswordService userDetailsPasswordService;
    protected final UserDetails retrieveUser(String username,
                                             UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();
        try {
            //重点来了!主要就在这里了!
            //可别忘了,咱们为什么要翻源码,是想用自己数据库中的数据实现认证操作啊!
            //UserDetails就是SpringSecurity自己的用户对象。
            //this.getUserDetailsService()其实就是得到UserDetailsService的一个实现类
            //loadUserByUsername里面就是真正的认证逻辑
            //也就是说我们可以直接编写一个UserDetailsService的实现类,告诉SpringSecurity就可以了!
            //loadUserByUsername方法中只需要返回一个UserDetails对象即可
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            //若返回null,就抛出异常,认证失败。
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("UserDetailsService returned
                                                                 null, which is an interface contract violation");
            } else {
                //若有得到了UserDetails对象,返回即可。
                return loadedUser;
            }
        } catch (UsernameNotFoundException var4) {
            this.mitigateAgainstTimingAttack(authentication);
            throw var4;
        } catch (InternalAuthenticationServiceException var5) {
            throw var5;
        } catch (Exception var6) {
            throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
        }
    }
}

AbstractUserDetailsAuthenticationProvider

按理说到此已经知道自定义认证方法的怎么写了,但咱们把返回的流程也大概走一遍,上面不是说到返回了一个 UserDetails对象对象吗?

跟着它就又回到AbstractUserDetailsAuthenticationProvider对象中authenticate方法最后一行了。

public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
   
    public Authentication authenticate(Authentication authentication) throws
    AuthenticationException {
        //最后一行返回值,调用了createSuccessAuthentication方法,此方法就在下面!
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }
    
    //咿!?怎么又封装了一次UsernamePasswordAuthenticationToken,开局不是已经封装过了吗?
    protected Authentication createSuccessAuthentication(Object principal, 
                                                         Authentication authentication, 
                                                         UserDetails user) {
        //那就从构造方法点进去看看,这才干啥了。
        UsernamePasswordAuthenticationToken result = new
        UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(),
                                            this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
        return result;
    }
}


相关文章
|
8月前
|
人工智能 Linux API
OpenClaw Skill开发实战从入门到精通:阿里云/本地部署与免费国产大模型适配实操指南
OpenClaw的Skill插件体系与多Agent协同架构,让AI工具的定制化变得轻量化、平民化,2026年版本对跨平台部署与国产大模型的深度适配,更是降低了个人开发者与中小团队的使用门槛。从简单的RSS抓取Skill开发,到全平台的OpenClaw部署,再到阿里云百炼免费大模型的适配,整个流程无需复杂的底层开发,只需遵循固定的规范与步骤,即可快速搭建专属的AI工具体系。未来,随着OpenClaw社区的不断发展,更多的优质Skill将被开发与共享,AI Agent的应用场景也将进一步延伸至办公、研发、运营等各个领域。
2452 4
|
2月前
|
人工智能 安全 测试技术
GPT‑6 Astra完整深度解析:计算机操作能力、基准跑分、API实操、第三方评测与落地风险指南
GPT‑6 Astra最大的进化,不再局限传统对话文本生成,而是实现**看懂屏幕画面、操控软件完成真实电脑工作的Computer Use计算机操作能力**。在ScreenSpot‑Pro、OSWorld2.0、Terminal‑Bench4.0、ARC‑AGI‑3多项专项基准测试取得跨越式提升,官方放出电路设计、三维建模、游戏开发大量惊艳Demo。
464 0
|
7月前
|
人工智能 弹性计算 API
2026年最新阿里云优惠券领取:迁云与出海补贴券、学生专属优惠券、百炼按量达标返券
2026年阿里云优惠券体系涵盖企业、学生及AI开发者。企业可申请迁云补贴与出海扶持金,最高可达5亿算力补贴和10万元出海补贴,降低迁移与出海成本;学生完成认证可领300元无门槛券,用于多种云产品;AI开发者可通过“百炼”平台参与按量达标返券活动,最高返200元,鼓励体验大模型服务。此外,阿里云还不定期推出满减券包、新客户专享券等福利。
|
传感器 机器学习/深度学习 人工智能
智能照明系统:技术、应用与未来趋势###
【10月更文挑战第15天】 智能照明,正以前所未有的速度革新着我们的世界。它不仅仅是灯泡亮度的简单调节,更是一种融合了先进科技与人性化设计的未来之光。本文将深入浅出地剖析智能照明系统的核心技术,探讨其在不同领域的广泛应用,并展望这一领域的未来发展蓝图。 ###
1214 2
|
12月前
|
数据采集 人工智能 安全
2025 年主流数据中台系统推荐,企业数据系统建设方案
摘要:在数字化转型中,数据中台是企业释放数据价值的核心载体。本文聚焦2025年主流数据中台系统,从全链路治理能力、部署灵活性、业务适配性三大维度,对比瓴羊Dataphin、腾讯WeData等产品的核心优势与适用场景,结合行业案例覆盖度、用户评价、权威认证分析市场表现。研究发现,各类产品特色鲜明,如瓴羊Dataphin兼具阿里方法论与AI能力,字节Dataleap擅长实时处理。文章提出企业选型需遵循业务目标导向、能力匹配、长期适配原则,明确建设路径。数据中台未来将呈现AI融合、轻量与专业并存等趋势,其核心价值始终是“以数据服务业务”,助力企业数字化转型。
|
11月前
|
机器学习/深度学习 自然语言处理 监控
淘宝API揭秘:如何让你的店铺在海量商品中脱颖而出?
在淘宝海量商品中突围,需借助API实现精细化运营。本文详解商品管理、搜索优化、数据智能与营销互动四大类API应用,助力商家提升效率、精准引流、科学决策,实现智能化升级。(238字)
|
安全 测试技术 Linux
浅谈黑客赚钱黑产
浅谈黑客赚钱黑产
如何把多个文件(夹)向上移动1层(或多层)(在批量复制前或后进行)
该文介绍了如何使用一个工具将四个文件夹内的所有文件合并到另一个文件夹中。工具可以从百度网盘或蓝奏云下载,提取码分别为qwu2和2r1z。操作步骤包括:打开工具,选择文件批量复制,设置源路径上移,确定设置后将文件夹拖入,导入文件,移除不需要的文件夹路径,最后执行移动操作。移动过程中会删除空文件夹,并在回收站中可见。
1364 1