Spring Security 6.x 系列(10)—— SecurityConfigurer 配置器及其分支实现源码分析(二)

发布时间 2023-12-09 14:47:33作者: java小奔奔

一、前言

在本系列文章:

Spring Security 6.x 系列(4)—— 基于过滤器链的源码分析(一)
中着重分析了Spring SecuritySpring Boot自动配置、 DefaultSecurityFilterChainFilterChainProxy 的构造过程。

Spring Security 6.x 系列(7)—— SecurityBuilder 继承链源码分析
中详细分析了Spring SecurityWebSecurityHttpSecurityAuthenticationManagerBuilder 三个构造器的公共继承链。

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的继承关系图:

在这里插入图片描述

未完待续~~~~!!!!

转自:https://www.suanlizi.com/kf/1733375939053752320.html