1、继承结构类图
AuthenticationManagerBuilder用于创建AuthenticationManager。 允许轻松构建内存身份验证,LDAP身份验证,基于JDBC的身份验证,添加UserDetailsService以及添加AuthenticationProvider。
public interface SecurityBuilder<O> {
/**
* Builds the object and returns it or null.
*
* @return the Object to be built or null if the implementation allows it.
* @throws Exception if an error occurred when building the Object
*/
O build() throws Exception;
}
ProviderManagerBuilder扩展了SecurityBuilder接口增加了一个方法并且限制了build()方法只能创建AuthenticationManager类型的对象。
public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>> extends
SecurityBuilder<AuthenticationManager> {
//根据传入的自定义AuthenticationProvider添加身份验证。
//由于AuthenticationProvider实现未知,因此必须在外部完成所有自定义,并立即返回ProviderManagerBuilder。
//请注意,如果在添加AuthenticationProvider时发生错误,则抛出异常。
//返回:ProviderManagerBuilder 允许向ProviderManagerBuilder提供进一步的身份验证
B authenticationProvider(AuthenticationProvider authenticationProvider);
}
authenticationProvider()方法在AuthenticationManagerBuilder中实现了,向AuthenticationProvider链表添加authenticationProvider。
public AuthenticationManagerBuilder authenticationProvider(
AuthenticationProvider authenticationProvider) {
this.authenticationProviders.add(authenticationProvider);
return this;
}
2、AbstractSecurityBuilder
一个基础SecurityBuilder实现类,它确保构建的对象只构建一次。
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
//cas确保只构建一次
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
//获取构建的对象。 如果尚未构建,则抛出异常。
public final O getObject() {
if (!this.building.get()) {
throw new IllegalStateException("This object has not been built");
}
return this.object;
}
//子类应该实现它来执行构建。
protected abstract O doBuild() throws Exception;
}
3、AbstractConfiguredSecurityBuilder
一个基本的SecurityBuilder,允许将SecurityConfigurer应用于它。 这使得修改SecurityBuilder的策略可以自定义并分解为许多SecurityConfigurer对象,这些对象具有比SecurityBuilder更具体的目标。
例如,SecurityBuilder可以构建DelegatingFilterProxy,但SecurityConfigurer可能会使用会话管理,基于表单的登录,授权等所需的过滤器填充SecurityBuilder。
AbstractConfiguredSecurityBuilder有两个apply()方法,将SecurityConfigurerAdapter或SecurityConfigurer应用于此SecurityBuilder并调用SecurityConfigurerAdapter #setBuilder(SecurityBuilder)方法。
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
throws Exception {
configurer.addObjectPostProcessor(objectPostProcessor);
configurer.setBuilder((B) this);
add(configurer);
return configurer;
}
public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}
这些通过apply()方法保存的SecurityConfigurer会在执行构建方法doBuild()中调用init()和configure()方法中执行。
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
在doBuild()方法中最后一步是调用performBuild()方法用于执行构建,需要由子类去实现。
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
4、AuthenticationManagerBuilder
用于创建AuthenticationManager。 这个类是通过AuthenticationConfiguration进行Spring定义的,请参考《Spring Security启动过程》。
允许轻松构建内存身份验证,LDAP身份验证,基于JDBC的身份验证,添加UserDetailsService以及添加AuthenticationProvider。
AuthenticationManagerBuilder定义了三个方法用于提供这三种身份验证通过使用apply()方法添加三种不同SecurityConfigurerAdapter用来配置自身。
//将内存身份验证添加到AuthenticationManagerBuilder并返回InMemoryUserDetailsManagerConfigurer以允许自定义内存中身份验证。
//此方法还确保UserDetailsService可用于getDefaultUserDetailsService()方法。
//请注意,其他UserDetailsService可能会覆盖此UserDetailsService作为默认值。
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
throws Exception {
return apply(new InMemoryUserDetailsManagerConfigurer<>());
}
//将LDAP身份验证添加到AuthenticationManagerBuilder并返回LdapAuthenticationProviderConfigurer以允许自定义LDAP身份验证。
//此方法不确保UserDetailsService可用于getDefaultUserDetailsService()方法。
public LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthentication()
throws Exception {
return apply(new LdapAuthenticationProviderConfigurer<>());
}
//将JDBC身份验证添加到AuthenticationManagerBuilder并返回JdbcUserDetailsManagerConfigurer以允许自定义JDBC身份验证。
//当使用持久性数据存储时,最好使用Flyway或Liquibase之类的东西添加配置外部的用户来创建模式并添加用户以确保这些步骤仅执行一次并且使用最佳SQL。
//此方法还确保UserDetailsService可用于getDefaultUserDetailsService()方法。
//请注意,其他UserDetailsService可能会覆盖此UserDetailsService作为默认值。
public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
throws Exception {
return apply(new JdbcUserDetailsManagerConfigurer<>());
}
//UserDetailsAwareConfigurer继承于SecurityConfigurerAdapter,
//依旧会调用AbstractConfiguredSecurityBuilder的apply()方法为AuthenticationManagerBuilder提供配置
private <C extends UserDetailsAwareConfigurer<AuthenticationManagerBuilder, ? extends UserDetailsService>> C apply(
C configurer) throws Exception {
this.defaultUserDetailsService = configurer.getUserDetailsService();
return (C) super.apply(configurer);
}
下面主要分析一下InMemoryUserDetailsManagerConfigurer和JdbcUserDetailsManagerConfigurer是如何对AuthenticationManagerBuilder进行配置的。
4.1、UserDetailsAwareConfigurer
在这个继承体系中,UserDetailsAwareConfigurer扩展了SecurityConfigurerAdapter增加了一个获取UserDetailsService实例的方法。
public abstract class UserDetailsAwareConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService>
extends SecurityConfigurerAdapter<AuthenticationManager, B> {
//获取UserDetailsService,如果不可用,则返回null
public abstract U getUserDetailsService();
}
UserDetailsService是加载用户特定数据的核心接口。它在整个框架中用作用户DAO,并且是DaoAuthenticationProvider使用的策略。该接口只需要一个只读方法,这简化了对新数据访问策略的支持。
public interface UserDetailsService {
//根据用户名找到用户。 在实际实现中,搜索可能区分大小写,或者不区分大小写,具体取决于实现实例的配置方式。
//在这种情况下,返回的UserDetails对象可能具有与实际请求的用户名不同的用户名。
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetails是提供核心用户信息的接口。出于安全目的,Spring Security不直接使用实现。 它们只是存储用户信息,稍后将其封装到Authentication对象中。 这允许将与安全无关的用户信息(例如电子邮件地址,电话号码等)存储在方便的位置。
具体实现必须特别注意确保强制执行每个方法的非空约定。 有关参考实现(您可能希望在代码中扩展或使用),请参阅org.springframework.security.core.userdetails.User。
public interface UserDetails extends Serializable {
//返回授予用户的权限。 不可以返回null
Collection<? extends GrantedAuthority> getAuthorities();
//返回用于验证用户身份的密码
String getPassword();
//返回用于验证用户身份的用户名,不可以返回null
String getUsername();
//指示用户的帐户是否已过期。 过期的帐户无法通过身份验证。
boolean isAccountNonExpired();
//指示用户是锁定还是解锁。 锁定的用户无法进行身份验证。
boolean isAccountNonLocked();
//指示用户的凭据(密码)是否已过期。 过期的凭据会阻止身份验证
boolean isCredentialsNonExpired();
//指示用户是启用还是禁用。 禁用的用户无法进行身份验证。
boolean isEnabled();
}
4.2、AbstractDaoAuthenticationConfigurer
提供了getUserDetailsService()和configure()方法的实现。
abstract class AbstractDaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, C extends AbstractDaoAuthenticationConfigurer<B, C, U>, U extends UserDetailsService>
extends UserDetailsAwareConfigurer<B, U> {
private DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
private final U userDetailsService;
//需要一个UserDetailsService
protected AbstractDaoAuthenticationConfigurer(U userDetailsService) {
this.userDetailsService = userDetailsService;
//为DaoAuthenticationProvider配置了一个必须的对象
provider.setUserDetailsService(userDetailsService);
if (userDetailsService instanceof UserDetailsPasswordService) {
this.provider.setUserDetailsPasswordService((UserDetailsPasswordService) userDetailsService);
}
}
//为此类添加ObjectPostProcessor
@SuppressWarnings("unchecked")
public C withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
addObjectPostProcessor(objectPostProcessor);
return (C) this;
}
//允许指定PasswordEncoder与DaoAuthenticationProvider一起使用。 默认是使用纯文本。
@SuppressWarnings("unchecked")
public C passwordEncoder(PasswordEncoder passwordEncoder) {
provider.setPasswordEncoder(passwordEncoder);
return (C) this;
}
public C userDetailsPasswordManager(UserDetailsPasswordService passwordManager) {
provider.setUserDetailsPasswordService(passwordManager);
return (C) this;
}
@Override
public void configure(B builder) throws Exception {
//使用祖先类SecurityConfigurerAdapter通过addObjectPostProcessor()方法添加的ObjectPostProcessor初始化对象
//可能返回应该使用的修改实例。
provider = postProcess(provider);
//将DaoAuthenticationProvider加入到AuthenticationManagerBuilder的authenticationProviders中
builder.authenticationProvider(provider);
}
//直接返回构造函数中的
public U getUserDetailsService() {
return userDetailsService;
}
}
在configure()方法中主要为ProviderManagerBuilder实例(在这里也就是指AuthenticationManagerBuilder)配置了一个DaoAuthenticationProvider,这个DaoAuthenticationProvider实现了InitializingBean接口,要求其成员变量userDetailsService不可为null,userDetailsService在AbstractDaoAuthenticationConfigurer构造方法中配置。
AuthenticationProvider表示此类可以处理特定的Authentication实现。
public interface AuthenticationProvider {
//使用与AuthenticationManager.authenticate(身份验证)相同的协议执行身份验证。
//返回:完全身份验证的对象,包括凭据。 如果AuthenticationProvider无法支持对传递的Authentication对象的身份验证,则可能返回null。
//在这种情况下,将尝试支持所呈现的Authentication类的下一个AuthenticationProvider。
//如果如果身份验证失败抛出AuthenticationException
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
//如果此AuthenticationProvider支持指示的Authentication对象,则返回true。
//返回true并不能保证AuthenticationProvider能够验证所呈现的Authentication类的实例。
//它只是表明它可以支持对它进行更密切的评估。 AuthenticationProvider仍然可以从authenticate(Authentication)方法返回null,
//以指示应该尝试另一个AuthenticationProvider。
//选择能够执行身份验证的AuthenticationProvider是在运行时ProviderManager进行的。
boolean supports(Class<?> authentication);
}
AbstractUserDetailsAuthenticationProvider是基本AuthenticationProvider,允许子类覆盖和使用UserDetails对象。该类旨在响应UsernamePasswordAuthenticationToken身份验证请求。验证成功后,将创建UsernamePasswordAuthenticationToken并将其返回给调用者。令牌将包括用户名的字符串表示形式或从认证存储库返回的UserDetails作为其主体。如果正在使用容器适配器,则使用String是合适的,因为它需要用户名的String表示。如果您需要访问经过身份验证的用户的其他属性(例如电子邮件地址,人性化的名称等),则使用UserDetails是合适的。由于不建议使用容器适配器,并且UserDetails实现提供了额外的灵活性,默认情况下会返回UserDetails 。要覆盖此默认值,请将setForcePrincipalAsString设置为true。
通过存储放置在UserCache中的UserDetails对象来处理缓存。这可以确保可以验证具有相同用户名的后续请求,而无需查询UserDetailsService。应该注意的是,如果用户似乎提供了错误的密码,将查询UserDetailsService以确认用于比较的最新密码。只有无状态应用程序才需要缓存。例如,在普通的Web应用程序中,SecurityContext存储在用户的会话中,并且不会在每个请求上重新验证用户。因此,默认缓存实现是NullUserCache。
下面看AbstractUserDetailsAuthenticationProvider实现的AuthenticationProvider的两个方法。
//实现AbstractUserDetailsAuthenticationProvider的AuthenticationProvider仅支持UsernamePasswordAuthenticationToken这种Authentication
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//这个方法是DaoAuthenticationProvider实现的,通过用户名查找UserDetails
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
//默认隐藏认证失败细节,统一抛出BadCredentialsException
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
//retrieveUser()方法必须返回非null
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try {
//检查isAccountNonLocked、isEnabled、isAccountNonExpired
preAuthenticationChecks.check(user);
//DaoAuthenticationProvider使用passwordEncoder对比密码
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
//缓存对象认证失败,会重新检索UserDetails认证,避免缓存过期引起认证失败
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
//检查isCredentialsNonExpired
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//创建一个认证成功的Authentication
return createSuccessAuthentication(principalToReturn, authentication, user);
}
//创建一个成功的身份验证对象。受保护的子类可以覆盖。
//子类通常会在返回的Authentication对象中存储用户提供的原始凭证(不是salted或编码的密码)。
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
// Ensure we return the original credentials the user supplied,
// so subsequent attempts are successful even with encoded passwords.
// Also ensure we return the original getDetails(), so that future
// authentication events after cache expiry contain the details
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
下面看DaoAuthenticationProvider的实现,主要看retrieveUser()方法内是使用了UserDetailsPasswordService来获取UserDetails,可以通过setUserDetailsService()方法设置或替换UserDetailsService。
public DaoAuthenticationProvider() {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
//比对密码
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
protected void doAfterPropertiesSet() throws Exception {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
@Override
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
4.3、UserDetailsServiceConfigurer
在configure()方法执行前加入了一个initUserDetailsService()方法,允许子类初始化UserDetailsService。 例如,它可能会添加用户,初始化架构等。
public class UserDetailsServiceConfigurer<B extends ProviderManagerBuilder<B>, C extends UserDetailsServiceConfigurer<B, C, U>, U extends UserDetailsService>
extends AbstractDaoAuthenticationConfigurer<B, C, U> {
public UserDetailsServiceConfigurer(U userDetailsService) {
super(userDetailsService);
}
@Override
public void configure(B builder) throws Exception {
initUserDetailsService();
super.configure(builder);
}
protected void initUserDetailsService() throws Exception {
}
}
4.4、UserDetailsManagerConfigurer
构造函数接收一个UserDetailsManager,这个UserDetailsManager是UserDetailsService的扩展,提供创建新用户和更新现有用户的能力。在initUserDetailsService()方法中会使用withUser()方法接收的UserDetails或UserBuilder通过UserDetailsManager来初始创建一些用户。
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);
}
@Override
protected void initUserDetailsService() throws Exception {
for (UserDetailsBuilder userBuilder : userBuilders) {
getUserDetailsService().createUser(userBuilder.build());
}
for (UserDetails userDetails : this.users) {
getUserDetailsService().createUser(userDetails);
}
}
//允许将用户添加到正在创建的UserDetailsManager。 可以多次调用此方法以添加多个用户。
@SuppressWarnings("unchecked")
public final C withUser(UserDetails userDetails) {
this.users.add(userDetails);
return (C) this;
}
//允许将用户添加到正在创建的UserDetailsManager。 可以多次调用此方法以添加多个用户。
@SuppressWarnings("unchecked")
public final C withUser(User.UserBuilder userBuilder) {
this.users.add(userBuilder.build());
return (C) this;
}
//允许将用户添加到正在创建的UserDetailsManager。 可以多次调用此方法以添加多个用户。
@SuppressWarnings("unchecked")
public final UserDetailsBuilder withUser(String username) {
UserDetailsBuilder userBuilder = new UserDetailsBuilder((C) this);
userBuilder.username(username);
this.userBuilders.add(userBuilder);
return userBuilder;
}
}
4.5、JdbcUserDetailsManagerConfigurer
配置AuthenticationManagerBuilder以进行JDBC身份验证。 它还允许轻松地将用户添加到用于身份验证和设置架构的数据库中。唯一需要的方法是dataSource(DataSource),其他所有方法都有合理的默认值。
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());
}
public JdbcUserDetailsManagerConfigurer<B> dataSource(DataSource dataSource)
throws Exception {
this.dataSource = dataSource;
getUserDetailsService().setDataSource(dataSource);
return this;
}
//设置用于按用户名查找用户的查询。select username,password,enabled from users where username = ?
public JdbcUserDetailsManagerConfigurer<B> usersByUsernameQuery(String query)
throws Exception {
getUserDetailsService().setUsersByUsernameQuery(query);
return this;
}
//设置用于通过用户名查找用户权限的查询。 例如:select username,authority from authorities where username = ?
public JdbcUserDetailsManagerConfigurer<B> authoritiesByUsernameQuery(String query)
throws Exception {
getUserDetailsService().setAuthoritiesByUsernameQuery(query);
return this;
}
//给定用户名的SQL语句,用于查询用户的组权限。 例如:
//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
public JdbcUserDetailsManagerConfigurer<B> groupAuthoritiesByUsername(String query)
throws Exception {
JdbcUserDetailsManager userDetailsService = getUserDetailsService();
userDetailsService.setEnableGroups(true);
userDetailsService.setGroupAuthoritiesByUsernameQuery(query);
return this;
}
//一个非空字符串前缀,将添加到从持久存储加载的角色字符串中(默认为“”)。
public JdbcUserDetailsManagerConfigurer<B> rolePrefix(String rolePrefix)
throws Exception {
getUserDetailsService().setRolePrefix(rolePrefix);
return this;
}
//设置缓存实现
public JdbcUserDetailsManagerConfigurer<B> userCache(UserCache userCache)
throws Exception {
getUserDetailsService().setUserCache(userCache);
return this;
}
@Override
protected void initUserDetailsService() throws Exception {
//加载数据库初始脚本
if (!initScripts.isEmpty()) {
getDataSourceInit().afterPropertiesSet();
}
super.initUserDetailsService();
}
@Override
public JdbcUserDetailsManager getUserDetailsService() {
return (JdbcUserDetailsManager) super.getUserDetailsService();
}
//填充允许存储用户和权限的默认架构。
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(initScripts.toArray(new Resource[initScripts.size()]));
return dbp;
}
private DataSourceInitializer getDataSourceInit() {
DataSourceInitializer dsi = new DataSourceInitializer();
dsi.setDatabasePopulator(getDatabasePopulator());
dsi.setDataSource(dataSource);
return dsi;
}
}
4.6、InMemoryUserDetailsManagerConfigurer
使用InMemoryUserDetailsManager完成用户在内存中的创建、更新等操作。
public class InMemoryUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>>
extends UserDetailsManagerConfigurer<B, InMemoryUserDetailsManagerConfigurer<B>> {
public InMemoryUserDetailsManagerConfigurer() {
super(new InMemoryUserDetailsManager(new ArrayList<>()));
}
}
AuthenticationManagerBuilder除了可以使用上面提到的内存身份验证,LDAP身份验证,基于JDBC的身份验证,还可以使用userDetailsService()方法传入一个userDetailsService来实现自定义的身份验证。
//根据传入的自定义UserDetailsService添加身份验证。然后返回DaoAuthenticationConfigurer以允许自定义身份验证。
//此方法还确保UserDetailsService可用于getDefaultUserDetailsService()方法。 请注意,其他UserDetailsService可能会覆盖此UserDetailsService作为默认值。
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return apply(new DaoAuthenticationConfigurer<>(
userDetailsService));
}
下面来看真正用于创建AuthenticationManager的performBuild()方法。performBuild()方法最终创建了一个ProviderManager,这个ProviderManager通过构造方法包含了通过apply()方法传入的AuthenticationProvider,
@Override
protected ProviderManager performBuild() throws Exception {
if (!isConfigured()) {
logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
return null;
}
ProviderManager providerManager = new ProviderManager(authenticationProviders,
parentAuthenticationManager);
if (eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
}
if (eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(eventPublisher);
}
//使用Spring装配providerManager
providerManager = postProcess(providerManager);
return providerManager;
}
5、ProviderManager
通过AuthenticationProviders列表迭代身份验证请求。
通常会按顺序尝试AuthenticationProviders,直到提供非null响应。非空响应表示提供程序有权决定身份验证请求,并且不会尝试其他提供程序。如果后续提供程序成功验证了请求,则会忽略较早的身份验证例外,并将使用成功的身份验证。如果没有后续提供程序提供非空响应或新的AuthenticationException,则将使用最后收到的AuthenticationException。如果没有提供程序返回非空响应,或者指示它甚至可以处理身份验证,则ProviderManager将抛出ProviderNotFoundException。还可以设置父AuthenticationManager,如果没有配置的提供程序可以执行身份验证,也将尝试此操作。这旨在支持命名空间配置选项,但不是通常需要的功能。
此过程的例外情况是提供程序抛出AccountStatusException,在这种情况下,不会查询列表中的其他提供程序。验证后,如果凭据实现了CredentialsContainer接口,则将从返回的Authentication对象中清除凭据。可以通过修改eraseCredentialsAfterAuthentication属性来控制此行为。
//尝试验证传递的Authentication对象。
//将继续尝试AuthenticationProviders列表,直到AuthenticationProvider指示它能够验证传递的Authentication对象的类型。
//然后将使用该AuthenticationProvider尝试进行身份验证。
//如果多个AuthenticationProvider支持传递的Authentication对象,则第一个能够成功验证Authentication对象的对象将确定结果,
//从而覆盖先前支持的AuthenticationProviders抛出的任何可能的AuthenticationException。
//成功验证后,将不会尝试后续的AuthenticationProviders。
//如果任何支持AuthenticationProvider的身份验证未成功,则将重新抛出最后一次抛出的AuthenticationException。
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
//忽略因为如果在调用parent之前没有发生其他异常,我们将抛出以下内容,
//即使子进程中的提供者已经处理了请求,父进程也可能抛出ProviderNotFound
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
//身份验证已完成。 从身份验证中删除凭据和其他机密数据
((CredentialsContainer) result).eraseCredentials();
}
//如果尝试了父AuthenticationManager并且成功,则它将发布AuthenticationSuccessEvent
//如果父AuthenticationManager已经发布,则此检查可防止重复的AuthenticationSuccessEvent
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
//Parent为null,或者未进行身份验证(或抛出异常)。
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
//如果尝试了父AuthenticationManager并且失败了,那么它将发布AbstractAuthenticationFailureEvent
//如果父AuthenticationManager已经发布它,则此检查可防止重复的AbstractAuthenticationFailureEvent
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}