AuthenticationManager、ProviderManager

本篇主要讲述如下几点:html

一、AuthenticationManager、ProviderManager和AuthenticationProvider三者之间的关系缓存

二、以UsernamePasswordAuthenticationFilter为例,如何使用AuthenticationProvider的子类AbstractUserDetailsAuthenticationProvider、安全

   DaoAuthenticationProvider来验证用户名密码session

三、Authentication、UserDetails的内部结构app

 

先来看一张时序图:ide

从上图能够看出验证逻辑为:函数

一、在UsernamePasswordAuthenticationFilter的attemptAuthentication()方法中,调用AuthenticationManager进行认证post

二、AuthenticationManager接收Authentication对象做为参数,并经过authenticate方法对其进行验证(实际由其实现类ProviderManager完成)this

三、在ProviderManagerauthenticate方法中,轮训成员变量List<AuthenticationProvider> providers。该providers中若是有一个spa

      AuthenticationProvider的supports函数返回true,那么就会调用该AuthenticationProvider的authenticate函数认证,若是认证成功则整个

      认证过程结束。若是不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证成功则为认证成功。

四、UsernamePasswordAuthenticationToken实现了Authentication,主要是将用户输入的用户名密码进行封装,并提供给

      AuthenticationManager进行验证,验证成功后,返回一个认证成功的UsernamePasswordAuthenticationToken对象

 

AuthenticationManager

AuthenticationManager是一个接口,是认证方法的入口,接收一个Authentication对象做为参数

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

 

ProviderManager

它是AuthenticationManager的一个实现类,实现了authenticate(Authentication authentication)方法,还有一个成员变量

List<AuthenticationProvider> providers

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { ...... private List<AuthenticationProvider> providers = Collections.emptyList(); public Authentication authenticate(Authentication authentication) throws AuthenticationException { ...... } }

 

AuthenticationProvider

AuthenticationProvider也是一个接口,包含两个函数authenticate和supports。当Spring Security默认提供的Provider不能知足需求的时候,能够经过实现AuthenticationProvider接口来扩展出不一样的认证提供者

public interface AuthenticationProvider { //经过参数Authentication对象,进行认证
 Authentication authenticate(Authentication authentication) throws AuthenticationException; //是否支持该认证类型
    boolean supports(Class<?> authentication); }

 

Authentication

Authentication是一个接口,经过该接口能够得到用户相关信息、安全实体的标识以及认证请求的上下文信息等

在Spring Security中,有不少Authentication的实现类。如UsernamePasswordAuthenticationToken、AnonymousAuthenticationToken和

RememberMeAuthenticationToken等等

一般不会被扩展,除非是为了支持某种特定类型的认证

public interface Authentication extends Principal, Serializable { //权限结合,可以使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin, ROLE_ADMIN")返回字符串权限集合
    Collection<? extends GrantedAuthority> getAuthorities(); //用户名密码认证时能够理解为密码
 Object getCredentials(); //认证时包含的一些信息。如remoteAddress、sessionId
 Object getDetails(); //用户名密码认证时可理解时用户名
 Object getPrincipal(); //是否被认证,认证为true 
 boolean isAuthenticated(); //设置是否被认证
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }

 

UserDetails

UserDetails也是一个接口,主要封装用户名密码是否过时、是否可用等信息

public interface UserDetails extends Serializable { //权限集合
         Collection<? extends GrantedAuthority> getAuthorities(); //密码 
 String getPassword(); //用户名
 String getUsername(); //用户名是否没有过时
 boolean isAccountNonExpired(); //用户名是否没有锁定 
 boolean isAccountNonLocked(); //用户密码是否没有过时
 boolean isCredentialsNonExpired(); //帐号是否可用(可理解为是否删除)
 boolean isEnabled(); }

 

接下来看具体的实现方法:

 ProviderManager

public Authentication authenticate(Authentication authentication) throws AuthenticationException { //获取当前的Authentication的认证类型
        Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); //遍历全部的providers
        for (AuthenticationProvider provider : getProviders()) { //判断该provider是否支持当前的认证类型。不支持,遍历下一个
            if (!provider.supports(toTest)) { continue; } if (debug) { logger.debug("Authentication attempt using "
                        + provider.getClass().getName()); } try { //调用provider的authenticat方法认证
                result = provider.authenticate(authentication); if (result != null) { //认证经过的话,将认证结果的details赋值到当前认证对象authentication。而后跳出循环
 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; } } ...... }

 

AbstractUserDetailsAuthenticationProvider

AbstractUserDetailsAuthenticationProvider是 AuthenticationProvider 的核心实现类 

public Authentication authenticate(Authentication authentication) throws AuthenticationException { //若是authentication不是UsernamePasswordAuthenticationToken类型,则抛出异常
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage( "AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // 获取用户名
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); //从缓存中获取UserDetails
        boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); //缓存中没有,则从子类DaoAuthenticationProvider中获取
        if (user == null) { cacheWasUsed = false; try { //获取用户信息。由子类DaoAuthenticationProvider实现
                user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } ...... } try { //前检查。由DefaultPreAuthenticationChecks实现(主要判断当前用户是否锁定,过时,冻结User)
 preAuthenticationChecks.check(user); //附加检查。由子类DaoAuthenticationProvider实现
 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { ...... } //后检查。由DefaultPostAuthenticationChecks实现(检测密码是否过时)
 postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (forcePrincipalAsString) { principalToReturn = user.getUsername(); } //将已经过验证的用户信息封装成 UsernamePasswordAuthenticationToken 对象并返回
        return createSuccessAuthentication(principalToReturn, authentication, user); }

一、前检查和后检查的参数为UserDetails,正好对应UserDetails中的4个isXXX方法

二、retrieveUser()和additionalAuthenticationChecks()由子类DaoAuthenticationProvider实现

三、createSuccessAuthentication以下:

protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { //从新封装成UsernamePasswordAuthenticationToken。包含用户名、密码,以及对应的权限 //该构造方法会给父类Authentication赋值: super.setAuthenticated(true)
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result; }

 

DaoAuthenticationProvider

DaoAuthenticationProvider实现了父类的retrieveUser()和additionalAuthenticationChecks()方法

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; try { //调用UserDetailsService接口的loadUserByUsername获取用户信息 //经过实现UserDetailsService接口来扩展对用户密码的校验
            loadedUser = this.getUserDetailsService().loadUserByUsername(username); } ...... //若是找不到该用户,则抛出异常
        if (loadedUser == null) { throw new InternalAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; }
@SuppressWarnings("deprecation") protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { Object salt = null; if (this.saltSource != null) { salt = this.saltSource.getSalt(userDetails); } //密码为空,则直接抛出异常
        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.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } }

关于UserDetailsService.loadUserByUsername方法,可参考Spring Security认证配置(一)

相关文章
相关标签/搜索