1. 前言

今天有个同学告诉我,在Security Learning项目的day11分支中出现了一个问题,验证码登录和其它登录不兼容了,出现了No Provider异常。还有这事?我赶紧跑了一遍还真是,看来我大意了,不过最终找到了原因,问题就出在AuthenticationManager的初始化上。自定义了一个UseDetailServiceAuthenticationProvider之后AuthenticationManager的默认初始化出问题了。

虽然在Spring Security 实战干货:图解认证管理器AuthenticationManager一文中对AuthenticationManager的流程进行了分析,但是还是不够深入,以至于出现了问题。今天就把这个坑补了。

2. AuthenticationManager的初始化

关于AuthenticationManager的初始化,流程部分请看这一篇文章,里面有流程图。在流程图中我们提到了AuthenticationManager的默认初始化是由AuthenticationConfiguration完成的,但是只是一笔带过,具体的细节没有搞清楚。现在就搞定它。

AuthenticationConfiguration

AuthenticationConfiguration初始化AuthenticationManager的核心方法就是下面这个方法:

public AuthenticationManager getAuthenticationManager() throws Exception {
// 先判断 AuthenticationManager 是否初始化
if (this.authenticationManagerInitialized) {
// 如果已经初始化 那么直接返回初始化的
return this.authenticationManager;
}
// 否则就去 Spring IoC 中获取其构建类
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
// 如果不是第一次构建 好像是每次总要通过Builder来进行构建
if (this.buildingAuthenticationManager.getAndSet(true)) {
// 返回 一个委托的AuthenticationManager
return new AuthenticationManagerDelegator(authBuilder);
}
// 如果是第一次通过Builder构建 将全局的认证配置整合到Builder中 那么以后就不用再整合全局的配置了
for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
authBuilder.apply(config);
}
// 构建AuthenticationManager
authenticationManager = authBuilder.build();
// 如果构建结果为null
if (authenticationManager == null) {
// 再次尝试去Spring IoC 获取懒加载的 AuthenticationManager Bean
authenticationManager = getAuthenticationManagerBean();
}
// 修改初始化状态
this.authenticationManagerInitialized = true;
return authenticationManager;
}

根据上面的注释,AuthenticationManager的初始化流程是清楚的。但是又引出来了两个问题,我将另起两个章节来分析这两个问题。

AuthenticationManagerBuilder

第一个问题是AuthenticationManagerBuilder是如何注入Spring IoC的?

AuthenticationManagerBuilder注入的过程也是在AuthenticationConfiguration中完成的,注入的是其内部的一个静态类DefaultPasswordEncoderAuthenticationManagerBuilder,这个类和Spring Security的主配置类WebSecurityConfigurerAdapter的一个内部类同名,这两个类几乎逻辑相同,没有什么特别的。具体使用哪个由WebSecurityConfigurerAdapter.disableLocalConfigureAuthenticationBldr决定。

其参数ObjectPostProcessor<T>抽空会讲它的作用。

GlobalAuthenticationConfigurerAdapter

另一个问题是GlobalAuthenticationConfigurerAdapter从哪儿来?

AuthenticationConfiguration包含下面自动注入GlobalAuthenticationConfigurerAdapter的方法:

@Autowired(required = false)
public void setGlobalAuthenticationConfigurers(
List<GlobalAuthenticationConfigurerAdapter> configurers) {
configurers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.globalAuthConfigurers = configurers;
}

该方法会根据它们各自的Order进行排序。该排序的意义在于AuthenticationManagerBuilder在执行构建AuthenticationManager时会按照排序的先后执行GlobalAuthenticationConfigurerAdapterconfigure方法。

全局认证配置

第一个为EnableGlobalAuthenticationAutowiredConfigurer,它目前除了打印一下初始化信息没有什么实际作用。

认证处理器初始化注入

第二个为InitializeAuthenticationProviderBeanManagerConfigurer,核心方法为其内部类的实现:

@Override
public void configure(AuthenticationManagerBuilder auth) {
//
// 如果存在 AuthenticationProvider 已经注入 或者 已经有AuthenticationManager被代理
if (auth.isConfigured()) {
return;
} // 尝试从Spring IoC获取 AuthenticationProvider
AuthenticationProvider authenticationProvider = getBeanOrNull(
AuthenticationProvider.class);
// 获取不到就中断
if (authenticationProvider == null) {
return;
}
// 获取得到就配置到AuthenticationManagerBuilder中,最终会配置到AuthenticationManager中
auth.authenticationProvider(authenticationProvider);
}

这里的getBeanOrNull方法如果不仔细看的话是有误区的,核心代码如下:

String[] userDetailsBeanNames = InitializeUserDetailsBeanManagerConfigurer.this.context
.getBeanNamesForType(type);
// Spring IoC 不能同时存在多个type相关类型的Bean 否则无法注入
if (userDetailsBeanNames.length != 1) {
return null;
}

如果 Spring IoC 容器中存在了多个AuthenticationProvider,那么这些AuthenticationProvider就不会生效。

用户详情管理器初始化注入

第三个为InitializeUserDetailsBeanManagerConfigurer,优先级低于上面。它的核心方法为:

public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
// 不能有多个 否则 就中断
UserDetailsService userDetailsService = getBeanOrNull(
UserDetailsService.class);
if (userDetailsService == null) {
return;
}
// 开始配置普通 密码认证器 DaoAuthenticationProvider
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class); DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
provider.afterPropertiesSet(); auth.authenticationProvider(provider);
}

InitializeAuthenticationProviderBeanManagerConfigurer流程差不多,只不过这里主要处理的是UserDetailsServiceDaoAuthenticationProvider。当执行到上面这个方法时,如果 Spring IoC 容器中存在了多个UserDetailsService,那么这些UserDetailsService就不会生效,影响DaoAuthenticationProvider的注入。

3. 真相大白

到此为什么在认证的时候找不到原因终于找到了,原来我在使用Spring Security默认配置时(注意这个前提),向Spring IoC注入了多个UserDetailsService导致DaoAuthenticationProvider没有生效。也就是说在一套配置中如果你存在多个UserDetailsService的Spring Bean将会影响DaoAuthenticationProvider的注入。

但是我仍然需要注入多个AuthenticationProvider怎么办?

首先把你需要配置的AuthenticationProvider注入Spring IoC,然后在HttpSecurity中这么写:

protected void configure(HttpSecurity http) throws Exception {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
CaptchaAuthenticationProvider captchaAuthenticationProvider = context.getBean("captchaAuthenticationProvider", CaptchaAuthenticationProvider.class);
http.authenticationProvider(captchaAuthenticationProvider);
// 省略
}

有几个AuthenticationProvider你就按照上面配置几个。

一般情况下一个UserDetailsService对应一个AuthenticationProvider

4. 总结

这一篇对于需要多种认证方式并存的Spring Security配置非常重要,如果你在配置中不注意,很容易引发No Provider ……的异常。所以有很有必要学习一下。

关注公众号:Felordcn 获取更多资讯

个人博客:https://felord.cn

Spring Security 实战干货:AuthenticationManager的初始化细节的更多相关文章

  1. Spring Security 实战干货:理解AuthenticationManager

    1. 前言 我们上一篇介绍了UsernamePasswordAuthenticationFilter的工作流程,留下了一个小小的伏笔,作为一个Servlet Filter应该存在一个doFilter实 ...

  2. Spring Security 实战干货:图解用户是如何登录的

    1. 前言 欢迎阅读Spring Security 实战干货系列文章,在集成Spring Security安全框架的时候我们最先处理的可能就是根据我们项目的实际需要来定制注册登录了,尤其是Http登录 ...

  3. Spring Security 实战干货: 简单的认识 OAuth2.0 协议

    1.前言 欢迎阅读 Spring Security 实战干货 系列文章 .OAuth2.0 是近几年比较流行的授权机制,对于普通用户来说可能每天你都在用它,我们经常使用的第三方登录大都基于 OAuth ...

  4. Spring Security 实战干货:实现自定义退出登录

    文章目录 1. 前言 2. 我们使用 Spring Security 登录后都做了什么 2. 退出登录需要我们做什么 3. Spring Security 中的退出登录 3.1 LogoutFilte ...

  5. Spring Security 实战干货:客户端OAuth2授权请求的入口

    1. 前言 在Spring Security 实战干货:OAuth2第三方授权初体验一文中我先对OAuth2.0涉及的一些常用概念进行介绍,然后直接通过一个DEMO来让大家切身感受了OAuth2.0第 ...

  6. Spring Security 实战干货:OAuth2登录获取Token的核心逻辑

    1. 前言 在上一篇Spring Security 实战干货:OAuth2授权回调的核心认证流程中,我们讲了当第三方同意授权后会调用redirectUri发送回执给我们的服务器.我们的服务器拿到一个中 ...

  7. Spring Security 实战干货:使用 JWT 认证访问接口

    (转载)原文链接:https://my.oschina.net/10000000000/blog/3127268 1. 前言 欢迎阅读Spring Security 实战干货系列.之前我讲解了如何编写 ...

  8. Spring Security 实战干货:如何实现不同的接口不同的安全策略

    1. 前言 欢迎阅读 Spring Security 实战干货 系列文章 .最近有开发小伙伴提了一个有趣的问题.他正在做一个项目,涉及两种风格,一种是给小程序出接口,安全上使用无状态的JWT Toke ...

  9. Spring Security 实战干货:图解Spring Security中的Servlet过滤器体系

    1. 前言 我在Spring Security 实战干货:内置 Filter 全解析对Spring Security的内置过滤器进行了罗列,但是Spring Security真正的过滤器体系才是我们了 ...

随机推荐

  1. Spring Data JPA 的 Specifications动态查询

    主要的结构: 有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询. ...

  2. kali2020.01修改root终端颜色

    kali2020.01非root用户的终端和root用户终端颜色存在较大差异: 修改配置,将非root用户的配置替换root用户,输入以下命令即可: cd /home/lijingrong //切换到 ...

  3. vue API 知识点(4) --- 指令、特殊 attribute 、内置组件

    一.指令 1.v-text <span v-text="msg"></span> <!-- 两种写法是一样的 --> <span>{ ...

  4. Spring Boot 中使用 Quartz 实现任务调度

    Quartz 概述 Quartz 是 OpenSymphony 开源组织在 Job Scheduling 领域又一个开源项目,它可以与 J2EE. J2SE 应用程序相结合也可以单独使用.Quartz ...

  5. Shiro认证详解

    Shiro shiro是一个java的安全框架 官网地址 http://shiro.apache.org/ 目录 Shiro Shiro综述 过滤器 注解 整合Shiro 1. 配置SecurityM ...

  6. Linux USB子系统(一)—— USB设备基础概念

    一.基础概念 在终端用户看来,USB设备为主机提供了多种多样的附加功能,如文件传输,声音播放等,但对USB主机来说,它与所有USB设备的接口都是一致的.一个USB设备由3个功能模块组成:USB总线接口 ...

  7. [leetcode]236. Lowest Common Ancestor of a Binary Tree树的最小公共祖先

    如果一个节点的左右子树上分别有两个节点,那么这棵树是祖先,但是不一定是最小的,但是从下边开始判断,找到后一直返回到上边就是最小的. 如果一个节点的左右子树上只有一个子树上遍历到了节点,那么那个子树可能 ...

  8. 【WPF】 问题总结-RaidButton修改样式模板后作用区域的变化

    最近工作需要,需要重绘RaidButton控件,具体想要达成的的效果是这样的: 当点击按钮任意一个地方的时候,按钮的背景改变. 于是我是这样对控件模板进行修改的: <Style x:Key=&q ...

  9. 【wp】2020XCTF_逆向

    前几天的XCTF最后一场终于打完了,三场比赛下来对逆向部分的大概感觉是从第一场的啥都不会做(一道lua+一道apk)到后来的终于能有参与度,至少后两场的题目都是pc逆向,虽然特殊架构但好歹能做(tcl ...

  10. PostgreSQL使用MySQL外表(mysql_fdw)

    postgres使用mysql外表 转载请注明出处https://www.cnblogs.com/funnyzpc/p/14223167.html 浅谈 postgres不知不觉已经升到了版本13,记 ...