跳到主要内容

03、Spring Security 实战 - 默认认证数据源是什么?

1. 查看 SpringBootWebSecurityConfiguration 类的 defaultSecurityFilterChain 方法

 

2. 进入 HttpSecurity 类的 formLogin 方法

 

3. 进入 FormLoginConfigurer类的 FormLoginConfigurer 方法

 

4. 进入 UsernamePasswordAuthenticationFilter 类的 attempAuthentication 方法

 

1. attemptAuthentication 方法
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
   
     
    
    private AuthenticationManager authenticationManager;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException {
   
     
        // 1、判断是否是 post 请求方式;
        if (this.postOnly && !request.getMethod().equals("POST")) {
   
     
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        // 2、从请求中根据表单name属性的值获取用户名 username
        String username = obtainUsername(request);
        username = (username != null) ? username : "";
        username = username.trim();
        // 3、从请求中根据表单name属性的值获取密码 password
        String password = obtainPassword(request);
        password = (password != null) ? password : "";
        // 4、将 username 和 password 封装成 UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        setDetails(request, authRequest);
        // 5、调用 AuthenticationManager 中的 authenticate 方法完成身份认证
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    @Nullable
    protected String obtainPassword(HttpServletRequest request) {
   
     
        return request.getParameter(this.passwordParameter);
    }

    @Nullable
    protected String obtainUsername(HttpServletRequest request) {
   
     
        return requesta.getParameter(this.usernameParameter);
    }

    protected AuthenticationManager getAuthenticationManager() {
   
     
        return this.authenticationManager;
    }
}

具体流程:

1、 判断是否是post请求方式;
2、 从请求中根据表单name属性的值获取用户名username;
3、 从请求中根据表单name属性的值获取密码password;
4、 将username和password封装成UsernamePasswordAuthenticationTokenauthRequest;
5、 调用AuthenticationManager接口中的authenticate(authRequest)方法完成身份认证;
6、 AuthenticationManager接口中的authenticate方法完成身份认证;

2. AuthenticationManager 接口

在上述方法中通过 AuthenticationManager 接口中的 authenticate(authRequest) 方法完成身份认证,事实上,AuthenticationManager 是认证相关的顶层接口,该接口中只有一个 authenticate 方法,请求体是一个包含认证信息的不完整的 Authentication 对象,响应体是一个包含凭据的完整 的Authentication 对象 。

public interface AuthenticationManager {
   
     
   Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager 的实现l类如下:

 
当我们使用debug模式按F7进入 attemptAuthentication 方法内部时,发现真正实现认证的是AuthenticationManager 接口的实现类 ProvideManager,调用实现类中的 authenticate 方法完成认证,AuthenticationManager 接口一般不直接认证,而是通过子类去认证。

下面我们使用debug模式按F7进入authenticate 方法,将进入ProvideManager的 authenticate 方法。

5. 进入 ProvideManager 类的 authenticate 方法

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
   
     

    // AuthenticationManager 接口
    private AuthenticationManager parent;

    // 存储AuthenticationProvider接口实现类的List集合
    private List<AuthenticationProvider> providers = Collections.emptyList();

    /**
     * @param authentication 待认证的对象
     * @return Authentication 认证成功后填充的对象
    */
    @Override
    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;
        int currentPosition = 0;
        int size = this.providers.size();
		// 遍历providers集合
        // 如果该providers中如果有一个AuthenticationProvider 的 supports 方法返回 true
        // 那么就会调用该 AuthenticationProvider 的 authenticate 方法认证
        // 如果认证成功则整个认证过程结束。
        // 如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证
        // 只要有一个认证成功则为认证成功。
        for (AuthenticationProvider provider : getProviders()) {
   
     
            if (!provider.supports(toTest)) {
   
     
                continue;
            }
            try {
   
     
                result = provider.authenticate(authentication);
                if (result != null) {
   
     
                    copyDetails(authentication, result);
                    break;
                }
            }catch (AccountStatusException | InternalAuthenticationServiceException ex) {
   
     
                prepareException(ex, authentication);
                throw ex;
            }catch (AuthenticationException ex) {
   
     
                lastException = ex;
            }
        }
        // 如果上述过程没有认证成功,result==null。
        // 并且 AuthenticationManager parent != null,那么会使用该 parent 继续认证。
        if (result == null && this.parent != null) {
   
     
            // Allow the parent to try.
            try {
   
     
                // 回调AuthenticationManager接口实现类ProviderManager中的该方法
                parentResult = this.parent.authenticate(authentication);
                result = parentResult;
            }catch (ProviderNotFoundException ex) {
   
     
            }catch (AuthenticationException ex) {
   
     
                parentException = ex;
                lastException = ex;
            }
        }
        // 省略...
    }
    
    public List<AuthenticationProvider> getProviders() {
   
     
        return this.providers;
    }
}

整个方法的认证流程:

认证是通过 AuthenticationManager 实现类 ProviderManage 的 authenticate 方法完成的,该方法会遍历 List<AuthenticationProvider> providers 集合获取 AuthenticationProvider 的实现类完成认证,如果该 providers 中如果有一个 AuthenticationProvider 的 supports 方法返回 true,那么就会调用该 AuthenticationProvider 的 authenticate 方法认证,如果认证成功则整个认证过程结束。如果不成功,则继续使用下一个合适的 AuthenticationProvider 进行认证,只要有一个认证成功则为认证成功。

如果上述过程没有认证成功,且该ProviderManager的成员变量AuthenticationManager parent不为null,那么会使用该 parent 继续认证。

1. ProviderManager类

ProviderManager 是AuthenticationManager 接口的具体实现类,对请求认证链进行管理。

AuthenticationManager 接口,ProviderManager类 ,AuthenticationProvider接口 的关系?
 
AuthenticationManager 是认证相关的核心接口,在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,所以说 AuthenticationManager一般不直接认证。

AuthenticationManager 接口的常用实现类 ProviderManager 内部会维护一个 List<AuthenticationProvider> providers 列表,存放多种认证方式,实际上这是委托者模式的应用。也就是说,核心的认证入口始终只有一个:AuthenticationManager。

不同的认证方式:用户名+密码(UsernamePasswordAuthenticationToken),邮箱+密码,手机号码+密码登录则对应了三个不同的 AuthenticationProvider。

2. AuthenticationProvider 接口

在ProviderManager 类的 authenticate 方法中,遍历List<AuthenticationProvider> providers 列表,AuthenticationProvider 将按照认证请求链顺序去执行,如果 result 非null 表示验证通过,不再尝试验证其它的 provider 进行认证;如果后续提供的 AuthenticationProvider 成功地对请求进行身份认证,则忽略先前的身份验证异常及null响应,并将使用成功的身份验证。

AuthenticationProvider接口和AuthenticationManager接口很相似,只多了一个supports方法,该接口通常是提供给开发人员实现,可以实现自定义的安全认证方式:

public interface AuthenticationProvider {
   
     
   // 认证方法
   Authentication authenticate(Authentication authentication) throws AuthenticationException;
   // 验证是否支持某种身份认证方式;
   boolean supports(Class<?> authentication);
}

ProviderManager中有一个List用来存储定义的 AuthenticationProvider 认证实现类,也可以认为是一个认证处理器链来支持同一个应用中的多个不同身份认证机制,ProviderManager 将会根据多个实现类的顺序来进行验证 。

AuthenticationProvider 接口的实现类:
 

3. 流程解析

authenticate 方法的核心流程:

for (AuthenticationProvider provider : getProviders()) {
   
     
    if (!provider.supports(toTest)) {
   
     
        continue;
    }
    try {
   
     
        // 调用AuthenticationProvider实现类的 authenticate 方法完成认证
        result = provider.authenticate(authentication);
    }
}
// 如果上述过程没有认证成功,result==null。
if (result == null && this.parent != null) {
   
     
    try {
   
     
        // 回调AuthenticationManager接口实现类ProviderManager中的该方法
        parentResult = this.parent.authenticate(authentication);
        result = parentResult;
    }
}

1. Authentication 待认证对象#

 

2. AnonymousAuthenticationProvider 类的 supports 方法#

 

调用AnonymousAuthenticationProvider类中的 supports 方法,判断是否支持UsernamePasswordAuthenticationToken 的认证:

// 匿名身份验证提供程序
public class AnonymousAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
   
     
   // ... 省略代码
    
   @Override
   public boolean supports(Class<?> authentication) {
   
     
      // 判断AnonymousAuthenticationToken对象是否是authentication对象的父类或接口
      // 判断AnonymousAuthenticationToken对象和authentication对象是否是同一个类或者同一个接口
      return (AnonymousAuthenticationToken.class.isAssignableFrom(authentication));
   }
}

AnonymousAuthenticationProvider 不支持 UsernamePasswordAuthenticationToken 认证方式,因为跳出循环,将回调 AuthenticationManager 实现类 ProviderManager 中的 authenticate 方法进行认证:

 

3. DaoAuthenticationProvider 类 supports 方法#

回调ProviderManager 类中的 authenticate 方法,此时 AuthenticationProvider的实现类为DaoAuthenticationProvider :

 

DaoAuthenticationProvider 继承抽象类 AbstractUserDetailsAuthenticationProvider:

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
   
     
}

执行AbstractUserDetailsAuthenticationProvider 类中的 supports() 方法,判断是否支持UsernamePasswordAuthenticationToken 的认证:

public abstract class AbstractUserDetailsAuthenticationProvider
    implements AuthenticationProvider, InitializingBean, MessageSourceAware {
   
     
    
    @Override
    public boolean supports(Class<?> authentication) {
   
     
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }
}

supports 方法将返回 true,开始调用 DaoAuthenticationProvider类中的 authenticate 方法执行 UsernamePasswordAuthenticationToken 的认证。

4. AbstractUserDetailsAuthenticationProvider 类的 authenticate 方法#

因为DaoAuthenticationProvider继承自抽象类 AbstractUserDetailsAuthenticationProvider ,因此实际调用的是 AbstractUserDetailsAuthenticationProvider 类的 authenticate 方法:

public abstract class AbstractUserDetailsAuthenticationProvider
    implements AuthenticationProvider, InitializingBean, MessageSourceAware {
   
     

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
   
     
        String username = determineUsername(authentication);
        boolean cacheWasUsed = true;
        // 先从缓存中获取认证成功的UserDeails对象
        UserDetails user = this.userCache.getUserFromCache(username);
        // 如果缓存中不存在
        if (user == null) {
   
     
            cacheWasUsed = false;
            try {
   
     
                // 从数据源中获取 
                user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
            } // ...
        }
        // ...
        // 将认证成功的UserDetails对象放入缓存中
        if (!cacheWasUsed) {
   
     
            this.userCache.putUserInCache(user);
        }
        // ...
    }     
}

看到这儿,我们的问题就快解决了,默认的数据源是什么?

5. DaoAuthenticationProvider 类的 retrieveUser 方法#

进入retrieveUser 方法的内部,调用了DaoAuthenticationProvider 中的 retrieveUser 方法:

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
   
     
    
    @Override
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
   
     
        prepareTimingAttackProtection();
        try {
   
     
            // 通过username获取 UserDetails 对象
            UserDetails loadedUser 
                = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
   
     
                throw new InternalAuthenticationServiceException(
                    "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        } // ...
    }
}

调用UserDeatailService 接口中的 loadUserByUsername 方法完成认证,UserDeatailService 的实现类:
 

public interface UserDetailsService {
   
     
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

debug模式下F7进入 loadUserByUsername 方法的内部,发现调用的是UserDetailsService 接口的实现类 InMemoryUserDetailsManager 中的 loadUserByUsername 方法。

public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
   
     
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   
     
        UserDetails user = this.users.get(username.toLowerCase());
        if (user == null) {
   
     
            throw new UsernameNotFoundException(username);
        }
        // 常见一个UserDetails对象并返回
        return new User(user.getUsername(), 
                        user.getPassword(), 
                        user.isEnabled(), 
                        user.isAccountNonExpired(),
                        user.isCredentialsNonExpired(), 
                        user.isAccountNonLocked(), 
                        user.getAuthorities());
    }
}

最终,我们终于找到了默认的数据源就是 InMemoryUserDetailsManager,基于内存的用户认证。

UserDetailService 是顶层父接口,用来修改默认认证的数据源信息,接口中 loadUserByUserName 方法是用来在认证时进行用户名认证方法,默认实现使用是内存实现,如果想要修改数据库实现我们只需要自定义 UserDetailService 实现,最终返回 UserDetails 实例即可。