用户自定义认证

简介: 本文深入分析Spring Security认证流程,从UsernamePasswordAuthenticationFilter过滤器入手,解析用户登录认证的实现机制,重点讲解AuthenticationManager与AuthenticationProvider的协作过程,并揭示DaoAuthenticationProvider如何通过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

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);

       }

   }

}

3.完整代码获取git仓库地址:https://github.com/Herbbbb/SpringSecurity.git分支名称:Day02-用户自定义认证


相关文章
|
12天前
|
数据采集 人工智能 安全
|
7天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
随机森林是一种基于决策树的集成学习算法,通过构建多棵决策树并结合它们的预测结果来提高准确性和稳定性。其核心思想包括两个随机性:Bootstrap采样(每棵树使用不同的训练子集)和特征随机选择(每棵树分裂时只考虑部分特征)。这种方法能有效处理大规模高维数据,避免过拟合,并评估特征重要性。随机森林的超参数如树的数量、最大深度等可通过网格搜索优化。该算法兼具强大预测能力和工程化优势,是机器学习中的常用基础模型。
344 164
|
6天前
|
机器学习/深度学习 自然语言处理 机器人
阿里云百炼大模型赋能|打造企业级电话智能体与智能呼叫中心完整方案
畅信达基于阿里云百炼大模型推出MVB2000V5智能呼叫中心方案,融合LLM与MRCP+WebSocket技术,实现语音识别率超95%、低延迟交互。通过电话智能体与座席助手协同,自动化处理80%咨询,降本增效显著,适配金融、电商、医疗等多行业场景。
345 155
|
7天前
|
编解码 人工智能 自然语言处理
⚽阿里云百炼通义万相 2.6 视频生成玩法手册
通义万相Wan 2.6是全球首个支持角色扮演的AI视频生成模型,可基于参考视频形象与音色生成多角色合拍、多镜头叙事的15秒长视频,实现声画同步、智能分镜,适用于影视创作、营销展示等场景。
581 4
|
15天前
|
SQL 自然语言处理 调度
Agent Skills 的一次工程实践
**本文采用 Agent Skills 实现整体智能体**,开发框架采用 AgentScope,模型使用 **qwen3-max**。Agent Skills 是 Anthropic 新推出的一种有别于mcp server的一种开发方式,用于为 AI **引入可共享的专业技能**。经验封装到**可发现、可复用的能力单元**中,每个技能以文件夹形式存在,包含特定任务的指导性说明(SKILL.md 文件)、脚本代码和资源等 。大模型可以根据需要动态加载这些技能,从而扩展自身的功能。目前不少国内外的一些框架也开始支持此种的开发方式,详细介绍如下。
1018 7