一、前言
在本系列文章:
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> {// 聲明了一個 providerprivate 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;}@Overridepublic 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}*/@Overridepublic 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);}@Overridepublic 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*/@Overrideprotected 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;}@Overrideprotected void initUserDetailsService() throws Exception {if (!this.initScripts.isEmpty()) {getDataSourceInit().afterPropertiesSet();}super.initUserDetailsService();}@Overridepublic 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
的繼承關系圖:
未完待續~~~~!!!!