一、前言
在本系列文章:
Spring Security 6.x 系列(4)—— 基于过滤器链的源码分析(一)
中着重分析了Spring Security
在Spring Boot
自动配置、 DefaultSecurityFilterChain
和FilterChainProxy
的构造过程。
Spring Security 6.x 系列(7)—— SecurityBuilder 继承链源码分析
中详细分析了Spring Security
中WebSecurity
、HttpSecurity
、AuthenticationManagerBuilder
三个构造器的公共继承链。
Spring Security 6.x 系列(8)—— SecurityConfigurer 配置器及其分支实现源码分析(一)
中分析SecurityConfigurer
配置器及其主要分支实现。
Spring Security 6.x 系列(9)—— 基于过滤器链的源码分析(二)
着重分析了@EnableGlobalAuthentication
注解的作用、对AuthenticationConfiguration
构造AuthenticationManager
过程和上文中未介绍的GlobalAuthenticationConfigurerAdapter
配置器的五个分支实现进行了详细的说明。
今天我们就从未被介绍的SecurityConfigurerAdapter
配置器的具体分支实现进行展开。
二、SecurityConfigurerAdapter
SecurityConfigurerAdapter
在上文中有过详解介绍,它是SecurityConfigurer
的基类,它允许子类仅实现它们感兴趣的方法。它还提供了使用 SecurityConfigurer
以及完成后获取正在配置的SecurityBuilder
(构造器)的访问权限的机制。
SecurityConfigurerAdapter
的实现主要有三大类:
UserDetailsAwareConfigurer
AbstractHttpConfigurer
LdapAuthenticationProviderConfigurer
考虑到 LDAP
现在使用很少,所以重点介绍前两个。
三、UserDetailsAwareConfigurer
这个类名就能大概知道是和用户详细信息配置有关。
再通过继承关系图,看看UserDetailsAwareConfigurer
的顶层架构设计:
UserDetailsAwareConfigurer
是一个抽象类,源码比较简单:
/**
* Base class that allows access to the {@link UserDetailsService} for using as a default
* value with {@link AuthenticationManagerBuilder}.
*
* @param <B> the type of the {@link ProviderManagerBuilder}
* @param <U> the type of {@link UserDetailsService}
* @author Rob Winch
*/
public abstract class UserDetailsAwareConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService>
extends SecurityConfigurerAdapter<AuthenticationManager, B> {
/**
* Gets the {@link UserDetailsService} or null if it is not available
* @return the {@link UserDetailsService} or null if it is not available
*/
public abstract U getUserDetailsService();
}
通过源码我们可知:
-
泛型
U
继承了UserDetailsService
接口,也就意味着
getUserDetailsService()
方法返回的对象肯定是UserDetailsService
接口的实现。 -
泛型
B
继承了ProviderManagerBuilder
接口,ProviderManagerBuilder
构造器的作用是用来构建AuthenticationManager
对象,可就意味UserDetailsAwareConfigurer
(配置器)用来配置ProviderManagerBuilder
构造器。
3.1 AbstractDaoAuthenticationConfigurer
AbstractDaoAuthenticationConfigurer
也是一个抽象类,是模版模式:
/**
* Allows configuring a {@link DaoAuthenticationProvider}
*
* @param <B> the type of the {@link SecurityBuilder}
* @param <C> the type of {@link AbstractDaoAuthenticationConfigurer} this is
* @param <U> The type of {@link UserDetailsService} that is being used
* @author Rob Winch
* @since 3.2
*/
public abstract class AbstractDaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, C extends AbstractDaoAuthenticationConfigurer<B, C, U>, U extends UserDetailsService>
extends UserDetailsAwareConfigurer<B, U> {
// 声明了一个 provider
private DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// 声明了一个 userDetailsService 的泛型属性
private final U userDetailsService;
/**
* 创建一个实例
* @param userDetailsService,userDetailsService的类型可以是UserDetailsService或者UserDetailsPasswordService
*/
AbstractDaoAuthenticationConfigurer(U userDetailsService) {
this.userDetailsService = userDetailsService;
this.provider.setUserDetailsService(userDetailsService);
if (userDetailsService instanceof UserDetailsPasswordService) {
this.provider.setUserDetailsPasswordService((UserDetailsPasswordService) userDetailsService);
}
}
/**
* Adds an {@link ObjectPostProcessor} for this class.
* @param objectPostProcessor
* @return the {@link AbstractDaoAuthenticationConfigurer} for further customizations
*/
@SuppressWarnings("unchecked")
public C withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return (C) this;
}
/**
* Allows specifying the {@link PasswordEncoder} to use with the
* {@link DaoAuthenticationProvider}. The default is to use plain text.
* @param passwordEncoder The {@link PasswordEncoder} to use.
* @return the {@link AbstractDaoAuthenticationConfigurer} for further customizations
*/
@SuppressWarnings("unchecked")
public C passwordEncoder(PasswordEncoder passwordEncoder) {
this.provider.setPasswordEncoder(passwordEncoder);
return (C) this;
}
public C userDetailsPasswordManager(UserDetailsPasswordService passwordManager) {
this.provider.setUserDetailsPasswordService(passwordManager);
return (C) this;
}
@Override
public void configure(B builder) throws Exception {
this.provider = postProcess(this.provider);
// 向builder添加provider(配置构造器阶段)
builder.authenticationProvider(this.provider);
}
/**
* Gets the {@link UserDetailsService} that is used with the
* {@link DaoAuthenticationProvider}
* @return the {@link UserDetailsService} that is used with the
* {@link DaoAuthenticationProvider}
*/
@Override
public U getUserDetailsService() {
return this.userDetailsService;
}
}
通过源码我们可知:
AbstractDaoAuthenticationConfigurer
初始时创建了一个DaoAuthenticationProvider
类型的AuthenticationProvider
实例。- 为使用者提供设置
DaoAuthenticationProvider
属性UserDetailsService
的功能并指定类型为:UserDetailsService/U serDetailsPasswordService
。 - 为使用者提供设置
DaoAuthenticationProvider
属性PasswordEncoder
功能; - 为使用者提供设置对象后置处处理器的功能。
AbstractDaoAuthenticationConfigurer
配置构造器对应的初始化阶段方法为空。AbstractDaoAuthenticationConfigurer
配置构造器对应的配置阶段方法:- 对
DaoAuthenticationProvider
执行后置处理 - 将
DaoAuthenticationProvider
添加到构造器中
- 对
3.2 DaoAuthenticationConfigurer
DaoAuthenticationConfigurer
继承自 AbstractDaoAuthenticationConfigurer
,在构造方法中调用AbstractDaoAuthenticationConfigurer
的构造方法。
/**
* Allows configuring a {@link DaoAuthenticationProvider}
*
* @param <B> The type of {@link ProviderManagerBuilder} this is
* @param <U> The type of {@link UserDetailsService} that is being used
* @author Rob Winch
* @since 3.2
*/
public class DaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService>
extends AbstractDaoAuthenticationConfigurer<B, DaoAuthenticationConfigurer<B, U>, U> {
/**
* Creates a new instance
* @param userDetailsService
*/
public DaoAuthenticationConfigurer(U userDetailsService) {
super(userDetailsService);
}
}
3.3 UserDetailsServiceConfigurer
UserDetailsServiceConfigurer
继承了AbstractDaoAuthenticationConfigurer
,并重写了AbstractDaoAuthenticationConfigurer
中的configure
方法,在configure
方法执行之前加入了initUserDetailsService
方法,以方便开发时按照自己的方式去初始化 UserDetailsService
。这里的initUserDetailsService
方法是空的,会交于子类进行具体实现,也是模版模式。
/**
* Allows configuring a {@link UserDetailsService} within a
* {@link AuthenticationManagerBuilder}.
*
* @param <B> the type of the {@link ProviderManagerBuilder}
* @param <C> the {@link UserDetailsServiceConfigurer} (or this)
* @param <U> the type of UserDetailsService being used to allow for returning the
* concrete UserDetailsService.
* @author Rob Winch
* @since 3.2
*/
public class UserDetailsServiceConfigurer<B extends ProviderManagerBuilder<B>, C extends UserDetailsServiceConfigurer<B, C, U>, U extends UserDetailsService>
extends AbstractDaoAuthenticationConfigurer<B, C, U> {
/**
* Creates a new instance
* @param userDetailsService the {@link UserDetailsService} that should be used
*/
public UserDetailsServiceConfigurer(U userDetailsService) {
super(userDetailsService);
}
@Override
public void configure(B builder) throws Exception {
initUserDetailsService();
super.configure(builder);
}
/**
* Allows subclasses to initialize the {@link UserDetailsService}. For example, it
* might add users, initialize schema, etc.
*/
protected void initUserDetailsService() throws Exception {
}
}
3.4 UserDetailsManagerConfigurer
UserDetailsManagerConfigurer
继承了UserDetailsServiceConfigurer
,并实现了 UserDetailsServiceConfigurer
中定义的initUserDetailsService
空方法,具体的实现逻辑就是将UserDetailsBuilder
所构建出来的 UserDetails
以及提前准备好的UserDetails
中的用户存储到UserDetailsService
中。
在实例构造上进一步限制了父类中的U userDetailsService
的类型为UserDetailsManager
。
该类同时添加 withUser
方法用来添加用户,同时还增加了一个UserDetailsBuilder
用来构建用户,这些逻辑都比较简单,大家可以自行查看。
/**
* Base class for populating an
* {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder}
* with a {@link UserDetailsManager}.
*
* @param <B> the type of the {@link SecurityBuilder} that is being configured
* @param <C> the type of {@link UserDetailsManagerConfigurer}
* @author Rob Winch
* @since 3.2
*/
public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C extends UserDetailsManagerConfigurer<B, C>>
extends UserDetailsServiceConfigurer<B, C, UserDetailsManager> {
private final List<UserDetailsBuilder> userBuilders = new ArrayList<>();
private final List<UserDetails> users = new ArrayList<>();
protected UserDetailsManagerConfigurer(UserDetailsManager userDetailsManager) {
super(userDetailsManager);
}
/**
* Populates the users that have been added.
* @throws Exception
*/
@Override
protected void initUserDetailsService() throws Exception {
for (UserDetailsBuilder userBuilder : this.userBuilders) {
getUserDetailsService().createUser(userBuilder.build());
}
for (UserDetails userDetails : this.users) {
getUserDetailsService().createUser(userDetails);
}
}
/**
* Allows adding a user to the {@link UserDetailsManager} that is being created. This
* method can be invoked multiple times to add multiple users.
* @param userDetails the user to add. Cannot be null.
* @return the {@link UserDetailsBuilder} for further customizations
*/
@SuppressWarnings("unchecked")
public final C withUser(UserDetails userDetails) {
this.users.add(userDetails);
return (C) this;
}
/**
* Allows adding a user to the {@link UserDetailsManager} that is being created. This
* method can be invoked multiple times to add multiple users.
* @param userBuilder the user to add. Cannot be null.
* @return the {@link UserDetailsBuilder} for further customizations
*/
@SuppressWarnings("unchecked")
public final C withUser(User.UserBuilder userBuilder) {
this.users.add(userBuilder.build());
return (C) this;
}
/**
* Allows adding a user to the {@link UserDetailsManager} that is being created. This
* method can be invoked multiple times to add multiple users.
* @param username the username for the user being added. Cannot be null.
* @return the {@link UserDetailsBuilder} for further customizations
*/
@SuppressWarnings("unchecked")
public final UserDetailsBuilder withUser(String username) {
UserDetailsBuilder userBuilder = new UserDetailsBuilder((C) this);
userBuilder.username(username);
this.userBuilders.add(userBuilder);
return userBuilder;
}
/**
* Builds the user to be added. At minimum the username, password, and authorities
* should provided. The remaining attributes have reasonable defaults.
*/
public final class UserDetailsBuilder {
private UserBuilder user;
private final C builder;
/**
* Creates a new instance
* @param builder the builder to return
*/
private UserDetailsBuilder(C builder) {
this.builder = builder;
}
/**
* Returns the {@link UserDetailsManagerConfigurer} for method chaining (i.e. to
* add another user)
* @return the {@link UserDetailsManagerConfigurer} for method chaining
*/
public C and() {
return this.builder;
}
/**
* Populates the username. This attribute is required.
* @param username the username. Cannot be null.
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
private UserDetailsBuilder username(String username) {
this.user = User.withUsername(username);
return this;
}
/**
* Populates the password. This attribute is required.
* @param password the password. Cannot be null.
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public UserDetailsBuilder password(String password) {
this.user.password(password);
return this;
}
/**
* Populates the roles. This method is a shortcut for calling
* {@link #authorities(String...)}, but automatically prefixes each entry with
* "ROLE_". This means the following:
*
* <code>
* builder.roles("USER","ADMIN");
* </code>
*
* is equivalent to
*
* <code>
* builder.authorities("ROLE_USER","ROLE_ADMIN");
* </code>
*
* <p>
* This attribute is required, but can also be populated with
* {@link #authorities(String...)}.
* </p>
* @param roles the roles for this user (i.e. USER, ADMIN, etc). Cannot be null,
* contain null values or start with "ROLE_"
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public UserDetailsBuilder roles(String... roles) {
this.user.roles(roles);
return this;
}
/**
* Populates the authorities. This attribute is required.
* @param authorities the authorities for this user. Cannot be null, or contain
* null values
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
* @see #roles(String...)
*/
public UserDetailsBuilder authorities(GrantedAuthority... authorities) {
this.user.authorities(authorities);
return this;
}
/**
* Populates the authorities. This attribute is required.
* @param authorities the authorities for this user. Cannot be null, or contain
* null values
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
* @see #roles(String...)
*/
public UserDetailsBuilder authorities(List<? extends GrantedAuthority> authorities) {
this.user.authorities(authorities);
return this;
}
/**
* Populates the authorities. This attribute is required.
* @param authorities the authorities for this user (i.e. ROLE_USER, ROLE_ADMIN,
* etc). Cannot be null, or contain null values
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
* @see #roles(String...)
*/
public UserDetailsBuilder authorities(String... authorities) {
this.user.authorities(authorities);
return this;
}
/**
* Defines if the account is expired or not. Default is false.
* @param accountExpired true if the account is expired, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public UserDetailsBuilder accountExpired(boolean accountExpired) {
this.user.accountExpired(accountExpired);
return this;
}
/**
* Defines if the account is locked or not. Default is false.
* @param accountLocked true if the account is locked, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public UserDetailsBuilder accountLocked(boolean accountLocked) {
this.user.accountLocked(accountLocked);
return this;
}
/**
* Defines if the credentials are expired or not. Default is false.
* @param credentialsExpired true if the credentials are expired, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public UserDetailsBuilder credentialsExpired(boolean credentialsExpired) {
this.user.credentialsExpired(credentialsExpired);
return this;
}
/**
* Defines if the account is disabled or not. Default is false.
* @param disabled true if the account is disabled, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public UserDetailsBuilder disabled(boolean disabled) {
this.user.disabled(disabled);
return this;
}
UserDetails build() {
return this.user.build();
}
}
}
3.5 JdbcUserDetailsManagerConfigurer
JdbcUserDetailsManagerConfigurer
继承了UserDetailsManagerConfigurer
,在父类的基础上补充了 DataSource
对象,同时还提供了相应的数据库查询方法。
/**
* Configures an
* {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder}
* to have JDBC authentication. It also allows easily adding users to the database used
* for authentication and setting up the schema.
*
* <p>
* The only required method is the {@link #dataSource(javax.sql.DataSource)} all other
* methods have reasonable defaults.
*
* @param <B> the type of the {@link ProviderManagerBuilder} that is being configured
* @author Rob Winch
* @since 3.2
*/
public class JdbcUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>>
extends UserDetailsManagerConfigurer<B, JdbcUserDetailsManagerConfigurer<B>> {
private DataSource dataSource;
private List<Resource> initScripts = new ArrayList<>();
public JdbcUserDetailsManagerConfigurer(JdbcUserDetailsManager manager) {
super(manager);
}
public JdbcUserDetailsManagerConfigurer() {
this(new JdbcUserDetailsManager());
}
/**
* Populates the {@link DataSource} to be used. This is the only required attribute.
* @param dataSource the {@link DataSource} to be used. Cannot be null.
* @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
* customizations
*/
public JdbcUserDetailsManagerConfigurer<B> dataSource(DataSource dataSource) {
this.dataSource = dataSource;
getUserDetailsService().setDataSource(dataSource);
return this;
}
/**
* Sets the query to be used for finding a user by their username. For example:
*
* <code>
* select username,password,enabled from users where username = ?
* </code>
* @param query The query to use for selecting the username, password, and if the user
* is enabled by username. Must contain a single parameter for the username.
* @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
* customizations
*/
public JdbcUserDetailsManagerConfigurer<B> usersByUsernameQuery(String query) {
getUserDetailsService().setUsersByUsernameQuery(query);
return this;
}
/**
* Sets the query to be used for finding a user's authorities by their username. For
* example:
*
* <code>
* select username,authority from authorities where username = ?
* </code>
* @param query The query to use for selecting the username, authority by username.
* Must contain a single parameter for the username.
* @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
* customizations
*/
public JdbcUserDetailsManagerConfigurer<B> authoritiesByUsernameQuery(String query) {
getUserDetailsService().setAuthoritiesByUsernameQuery(query);
return this;
}
/**
* An SQL statement to query user's group authorities given a username. For example:
*
* <code>
* select
* g.id, g.group_name, ga.authority
* from
* groups g, group_members gm, group_authorities ga
* where
* gm.username = ? and g.id = ga.group_id and g.id = gm.group_id
* </code>
* @param query The query to use for selecting the authorities by group. Must contain
* a single parameter for the username.
* @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
* customizations
*/
public JdbcUserDetailsManagerConfigurer<B> groupAuthoritiesByUsername(String query) {
JdbcUserDetailsManager userDetailsService = getUserDetailsService();
userDetailsService.setEnableGroups(true);
userDetailsService.setGroupAuthoritiesByUsernameQuery(query);
return this;
}
/**
* A non-empty string prefix that will be added to role strings loaded from persistent
* storage (default is "").
* @param rolePrefix
* @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
* customizations
*/
public JdbcUserDetailsManagerConfigurer<B> rolePrefix(String rolePrefix) {
getUserDetailsService().setRolePrefix(rolePrefix);
return this;
}
/**
* Defines the {@link UserCache} to use
* @param userCache the {@link UserCache} to use
* @return the {@link JdbcUserDetailsManagerConfigurer} for further customizations
*/
public JdbcUserDetailsManagerConfigurer<B> userCache(UserCache userCache) {
getUserDetailsService().setUserCache(userCache);
return this;
}
@Override
protected void initUserDetailsService() throws Exception {
if (!this.initScripts.isEmpty()) {
getDataSourceInit().afterPropertiesSet();
}
super.initUserDetailsService();
}
@Override
public JdbcUserDetailsManager getUserDetailsService() {
return (JdbcUserDetailsManager) super.getUserDetailsService();
}
/**
* Populates the default schema that allows users and authorities to be stored.
* @return The {@link JdbcUserDetailsManagerConfigurer} used for additional
* customizations
*/
public JdbcUserDetailsManagerConfigurer<B> withDefaultSchema() {
this.initScripts.add(new ClassPathResource("org/springframework/security/core/userdetails/jdbc/users.ddl"));
return this;
}
protected DatabasePopulator getDatabasePopulator() {
ResourceDatabasePopulator dbp = new ResourceDatabasePopulator();
dbp.setScripts(this.initScripts.toArray(new Resource[0]));
return dbp;
}
private DataSourceInitializer getDataSourceInit() {
DataSourceInitializer dsi = new DataSourceInitializer();
dsi.setDatabasePopulator(getDatabasePopulator());
dsi.setDataSource(this.dataSource);
return dsi;
}
}
在实例构造上进一步限制了父类中的U userDetailsService
的类型为JdbcUserDetailsManager
。
JdbcUserDetailsManager
的继承关系图:
3.6 InMemoryUserDetailsManagerConfigurer
InMemoryUserDetailsManagerConfigurer
继承了UserDetailsManagerConfigurer
,在实例构造上进一步限制了父类中的U userDetailsService
的类型为InMemoryUserDetailsManager
。
/**
* Configures an
* {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder}
* to have in memory authentication. It also allows easily adding users to the in memory
* authentication.
*
* @param <B> the type of the {@link ProviderManagerBuilder} that is being configured
* @author Rob Winch
* @since 3.2
*/
public class InMemoryUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>>
extends UserDetailsManagerConfigurer<B, InMemoryUserDetailsManagerConfigurer<B>> {
/**
* Creates a new instance
*/
public InMemoryUserDetailsManagerConfigurer() {
super(new InMemoryUserDetailsManager(new ArrayList<>()));
}
}
InMemoryUserDetailsManager
的继承关系图:
未完待续~~~~!!!!