官网地址 Spring Security Reference
版本:Version 5.5.0
配置器之 SecurityConfigurerAdapter 分支
在上一篇文章中提到了配置器顶级接口 SecurityConfigurer 有三个分支,其中实现类最多的(也是最常用的)就是 SecurityConfigurerAdapter 这个分支下的配置器,这将是本章的主要内容。
概述
先简单回顾以下上一篇文章中提到的 SecurityConfigurerAdapter :
源码注释:SecurityConfigurer 的基础实现类,它允许子类只实现他们感兴趣的方法。它还提供了一种机制,用于使用 SecurityConfigurer 并在完成后获得对正在配置的 SecurityBuilder 的访问权限。
总结
- 允许子类只实现他们感兴趣的方法。
- and 方法可以再使用完配置器后返回正在配置的构造器。
- setBuilder 方法是由构造器 apply 方法中自动调用的。
- 有个复合后置处理器内部类,维护一个 List 集合,可存储多个后置处理器对象
那么它的子类都具有它的特性,并且子类之争对自己感兴趣的点进行扩展,它主要有三个分支:
- UserDetailsAwareConfigurer
- AbstractHttpConfigurer
- LdapAuthenticationProviderConfigurer
根据这个类图可以看出来它们都有各子的侧重点:
- AbstractHttpConfigurer 关注于配置 HttpSecurityBuilder 构造器
- LdapAuthenticationProviderConfigurer 关注于配置 ProviderManagerBuilder 构造器
- AbstractHttpConfigurer 也关注与于 配置 ProviderManagerBuilder 构造器 ,但是特别之处在于它还内含了 UserDetailsService 用于获取用户信息的服务类。
ProviderManagerBuilder 是用来构造 AuthenticationManager 的。
AbstractHttpConfigurer
这个抽象类很简单,官方注释也十分简介:
为在 HttpSecurity 上运行的 SecurityConfigurer 实例添加一个方便的基类。
- HttpSecurity 是一个构造器,最终构建一个 DefaultSecurityFilterChain 类的对象。
这个基类在 AbstractHttpConfigurer 父类的基础上又扩展了两个方法:
-
disable 方法:通过删除一个配置器来实现禁用这个配置器的能力
public B disable() { /** * 调用父类的 getBuilder 方法获取正在配置的构造器实例, * 并调用构造器的 removeConfigurer 方法, * 从构造器的配置器集合中删除当前这个配置器 * 最后返回正在配置的构造器实例 */ getBuilder().removeConfigurer(getClass()); return getBuilder(); }
HttpSecurity (构造器)调用 removeConfigurer 方法根据 AbstractHttpConfigurer (配置器)类名删除指定的配置器。
配置器中的 removeConfigurer 方法逻辑很简单:
(摘自AbstractConfiguredSecurityBuilder源码)
-
withObjectPostProcessor 方法:添加(追加)后置处理器对象
public T withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) { /** * 向父类的复合后置处理器对象中的 List 中添加一个后置处理器 */ addObjectPostProcessor(objectPostProcessor); return (T) this; }
这个方法实际上调用了父类的 addObjectPostProcessor 方法,根据上一篇文章中的内容可以知道:这类构造器维护一个后置处理器链表(List),这里只是向链表中添加了一个后置处理器。
AbstractHttpConfigurer 的子类
上图中左侧是 HttpSecurity 的部分方法,右侧是 AbstractHttpConfigurer 的子类
可以看到,在 HttpSecurity 中可以调用各种方法将 AbstractHttpConfigurer 的子配置类应用到 HttpSecurity 上。所以说 AbstractHttpConfigurer 为在 HttpSecurity 上运行的 SecurityConfigurer 实例添加了一个方便的基类。
各个子类的内容参考:
(待补充,以后遇到一个补充一个)
UserDetailsAwareConfigurer
UserDetailsAwareConfigurer 和 AbstractDaoAuthenticationConfigurer
UserDetailsAwareConfigurer 是一个抽象类,允许访问 UserDetailsService
以用作 AuthenticationManagerBuilder
的默认值。
public abstract class UserDetailsAwareConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService>
extends SecurityConfigurerAdapter<AuthenticationManager, B> {
public abstract U getUserDetailsService();
}
它只有一个抽象方法,getUserDetailsService()
返回值是一个 UserDetailsService
。通过泛型约束可以看出,这个分支下的所以配置类都是用来配置 ProviderManagerBuilder
的并且它们都有一个默认的 UserDetailsService
用来获取用户的详细信息。
UserDetailsAwareConfigurer 的唯一子类 AbstractDaoAuthenticationConfigurer:
AbstractDaoAuthenticationConfigurer 有两个成员变量
private DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
直接为成员变量设置了初始值 DaoAuthenticationProvider 实例provider 成员是类的一个实例变量,不是类变量,即只有创建对应的实例时才会对它进行赋值操作
private final U userDetailsService;
这个是父类中提到的可访问的 UserDetailsService 对象,这个变量通过构造方法设置,设置后不再更改(final 修饰)。
也就是说,每一个 AbstractDaoAuthenticationConfigurer 子类都会产生一个 DaoAuthenticationProvider ,并且绑定一个 UserDetailsService 再用这个配置类配置 ProviderManagerBuilder
这样 ProviderManager 就可以管理多个认证器。
AbstractDaoAuthenticationConfigurer 中的方法都很简单,这个配置器抽象类只重写了 SecurityConfigurerAdapter 的 configure() 方法:
@Override
public void configure(B builder) throws Exception {
provider = postProcess(provider);
// 为将要构建的 AuthenticationManager 添加 provider
builder.authenticationProvider(provider);
}
关于 ProviderManagerBuilder 可以参考之前的文章:【Spring Security】—— AuthenticationManagerBuilder
DaoAuthenticationConfigurer 和 UserDetailsServiceConfigurer
它们都继承了 AbstractDaoAuthenticationConfigurer ,DaoAuthenticationConfigurer 相对简单,它只有一个构造方法,通过构造方法可以指定了 UserDetailsService 实例而已,其他逻辑与父类相同:
UserDetailsServiceConfigurer 除了可以通过构造函数指定 UserDetailsService 实例外, 它还重写了 AbstractDaoAuthenticationConfigurer 中的 configure 方法,在调用父类的 configure 方法之前加入了 initUserDetailsService 方法,以方便自定义初始化 UserDetailsService。但是这里的 initUserDetailsService 方法是空方法,具体的实现逻辑交给子类自行实现:
UserDetailsManagerConfigurer
UserDetailsManagerConfigurer 继承了 UserDetailsServiceConfigurer 并重写了父类定义的 initUserDetailsService 方法,对这个空方法做了基本的实现,具体的实现逻辑就是将(内部类)UserDetailsBuilder 所构建出来的 UserDetails 以及提前准备好的 UserDetails 中的用户存储到 UserDetailsService 中。
它有两个成员变量,如上图中使用到的两个变量,它们用来存储准备的加入 UserDetailsService 的用户:
private final List<UserDetailsBuilder> userBuilders = new ArrayList<>();
private final List<UserDetails> users = new ArrayList<>();
【UserDetailsBuilder 所构建出来的 UserDetails】 和 【提前准备好的 UserDetails】都是通过 UserDetailsManagerConfigurer 的 withUser 方法添加的,这些逻辑都比较简单,可以直接看源码。
UserDetailsManagerConfigurer 有两个子类:
- JdbcUserDetailsManagerConfigurer
- InMemoryUserDetailsManagerConfigurer
它们的源码比较简单,直接点开就能明白。
LdapAuthenticationProviderConfigurer
Ldap 额外依赖
在点开 LdapAuthenticationProviderConfigurer 的源码时,会发现有很对类找不到,这是英文要使用 Ldap 认证处理 spring-security 基础依赖外,还需要添加一些 Ldap 相关的依赖:
<!-- 使用 Ldap 认证需要的依赖 start -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
</dependency>
<!-- 使用 Ldap 认证需要的依赖 end -->
Ldap 介绍
在看源码之前需要简单了解一下 Ldap 是什么?
先在百度百科上搜以下:
- LDAP 是 Light Directory Access Portocol 的缩写,中文名称:轻型目录访问协议
- 中立的,意思应该是说这个协议可以跨平台,于平台无关
- 应用协议,意思应该是说它属于应用层协议
- 通过IP协议控制目录信息
- 它是一种有层次的、树形结构
这只是简单了解了 Ldap 大概是什么,其实 Ldap 也可以理解成数据库,只是它与传统数据库的结构不同,再加上它的层次结构特性,所以它也常用做权限控制及单点登录,所以SpringSecurity中也提供这种认证方式的配置入口。
在文章开始【概述】中提到过这个类的作用是配置 ProviderManagerBuilder 构造器,而 ProviderManagerBuilder 是用于构造认证器的。所以 LdapAuthenticationProviderConfigurer 最终是用来配置一个 Ldap 认证器的配置类。
关于 Ldap 的详细内容可以参考:
LdapAuthenticationProviderConfigurer 源码
它有两个内部类:
-
ContextSourceBuilder
Allows building a BaseLdapPathContextSource and optionally creating an embedded LDAP instance.
这个内部类的作用是构造一个 BaseLdapPathContextSource
- 默认 Ldap 认证服务端口:33389
- 默认 Ldap 认证服务地址:127.0.0.1
- 默认 Ldap 目录服务的根为:dc=springframework,dc=org
我理解 BaseLdapPathContextSource 就是一个 Ldap 环节上下文,在构建之前可以通过 ContextSourceBuilder 来设定连接信息。
-
PasswordCompareConfigurer
这个内部类的作用是配置密码比较器,包括加密器、密码属性名称(默认userPassword)
但是如果调用 passwordCompare 方法(这个是 LdapAuthenticationProviderConfigurer 的方法)来获取 PasswordCompareConfigurer 进行自定义配置,在不额外设置密码属性时,默认会将密码属性改成 password 。
主要方法:
-
configure 方法
这个不用多少,这是配置器真正完成配置的方法,由构造器构架对象的适合调用的。这里会发现,这个类没有 init 方法,这说明 Ldap 认证配置不关心初始化,初始化方法使用的是父类中的空方法。@Override public void configure(B builder) throws Exception { LdapAuthenticationProvider provider = postProcess(build()); builder.authenticationProvider(provider); }
这个法方法的逻辑很简单,先构建出一个 LdapAuthenticationProvider 实例,在把这个实例交给构造器。
-
build 方法
这个方法不要和构造器的 build 方法搞混了,它们没啥关系。这个方法在构造器的 configure 方法中被调用,作用是产生一个 LdapAuthenticationProvider 实例。private LdapAuthenticationProvider build() throws Exception { //调用 getContextSource 获取 BaseLdapPathContextSource 实例 BaseLdapPathContextSource contextSource = getContextSource(); /** * 利用 BaseLdapPathContextSource 实例 * 调用 createLdapAuthenticator 方法创建 LdapAuthenticator 实例(Ldap认证器实例) */ LdapAuthenticator ldapAuthenticator = createLdapAuthenticator(contextSource); //调用 getLdapAuthoritiesPopulator 方法 根据成员属性获得一个 DefaultLdapAuthoritiesPopulator 实例 LdapAuthoritiesPopulator authoritiesPopulator = getLdapAuthoritiesPopulator(); // 然后构造出(new) LdapAuthenticationProvider LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider( ldapAuthenticator, authoritiesPopulator); ldapAuthenticationProvider.setAuthoritiesMapper(getAuthoritiesMapper()); if (userDetailsContextMapper != null) { ldapAuthenticationProvider .setUserDetailsContextMapper(userDetailsContextMapper); } return ldapAuthenticationProvider; }