用户自定义认证

简介: 本文深入分析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-用户自定义认证


相关文章
|
26天前
|
存储 缓存 安全
One Trick Per Day
本文总结Java开发中六大关键规范:避免HashMap初始化容量设置不当,禁用Executors创建线程池以防OOM,Arrays.asList不可变操作,优先使用entrySet遍历Map,SimpleDateFormat非线程安全需避免static,以及并发修改记录时合理加锁。遵循这些实践可提升系统稳定性与性能。
One Trick Per Day
|
26天前
|
存储 搜索推荐 数据库
ES分布式搜索引擎入门
本课程学习Elasticsearch核心知识,包括倒排索引原理、IK分词器使用、Java Client操作索引的增删改查、批量导入、搜索查询(Term、全文、布尔查询)及排序分页等技能,掌握其在海量数据搜索中的高性能优势与实际应用。
 ES分布式搜索引擎入门
|
26天前
|
SQL 运维 监控
如何做好SQL质量监控
git-poison基于go-git实现分布式bug追溯,解决多分支开发中bug漏修、漏发问题。通过“投毒-解毒-银针”机制,自动化卡点发布流程,降低协同成本,提升发布安全性与效率,已在大型团队落地应用。
|
26天前
|
消息中间件 存储 Java
异步消息组件MQ高级
本文围绕消息中间件(如RabbitMQ)的可靠性机制展开,涵盖生产者重试与确认、消费者确认、消息持久化、失败处理、幂等性及延迟消息等核心内容。重点讲解如何通过重试机制、Confirm/Return确认模式保障消息发送可靠;通过手动或自动ACK确保消费可靠性;并结合定时任务实现失败消息重发,最终构建高可用的消息系统。
异步消息组件MQ高级
|
26天前
|
消息中间件 Java 数据安全/隐私保护
异步消息组件MQ基础
本课程介绍RabbitMQ在微服务中的应用,涵盖MQ的应用场景、异步调用与同步调用的区别、RabbitMQ的安装与配置、消息收发入门程序、工作队列、发布订阅模型及多种交换机(fanout、Direct、Topic)特性,同时讲解惰性队列、优先级队列、消息堆积处理及商城项目中的实际应用,帮助学员掌握消息中间件的核心技术与实践能力。
异步消息组件MQ基础
|
26天前
|
负载均衡 Java 应用服务中间件
微服务网关与配置中心
本课程学习微服务网关核心功能,包括使用Spring Cloud Gateway实现路由转发、负载均衡及全局过滤器应用。掌握通过Nacos进行服务发现与配置管理,实现统一鉴权、用户身份校验及请求头信息传递,并完成前后端联调与配置热更新等实战操作。
微服务网关与配置中心
|
26天前
|
运维 Devops 开发工具
生产环境缺陷管理
针对大型团队中多分支开发导致的bug管理难题,本文介绍基于go-git实现的通用化工具git-poison。通过“投毒-解毒-银针”机制,实现bug的分布式追溯与自动化卡点,有效避免漏修复、漏发布等问题,降低协同成本,提升发布安全性与效率。
 生产环境缺陷管理
|
26天前
|
Java 数据库 微服务
微服务服务注册与发现
本课程以黑马商城项目为案例,系统讲解单体架构与微服务架构的优缺点,深入剖析微服务、分布式及云原生架构的核心理念与技术实现。通过搭建项目环境、拆分商品与购物车服务、使用Nacos实现服务注册发现、OpenFeign远程调用等实践,掌握微服务开发全流程。
微服务服务注册与发现
|
26天前
|
SQL Java 数据库连接
持久层框架MyBatisPlus
MyBatisPlus是MyBatis的增强工具,简化单表CRUD操作,通过继承BaseMapper即可实现增删改查。支持条件构造器(如QueryWrapper)、分页插件、代码生成器等功能,提升开发效率,降低SQL冗余,广泛应用于企业级项目中。
 持久层框架MyBatisPlus
|
26天前
|
Java 测试技术 Linux
生产环境发布管理
本文介绍大型团队如何通过自动化部署平台实现多环境(dev/test/pre/prod)高效发布。涵盖各环境职责、基于Jenkins+K8S的CI/CD流程、分支管理与一键部署,并结合Skywalking等工具实现日志链路追踪与快速排错,提升发布效率与系统稳定性。
 生产环境发布管理