• AuthenticationManager、ProviderManager


    本篇主要讲述以下几点:

    1、AuthenticationManager、ProviderManager和AuthenticationProvider三者之间的关系

    2、以UsernamePasswordAuthenticationFilter为例,如何使用AuthenticationProvider的子类AbstractUserDetailsAuthenticationProvider、

       DaoAuthenticationProvider来验证用户名密码

    3、Authentication、UserDetails的内部结构

    先来看一张时序图:

    从上图可以看出验证逻辑为:

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

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

    3、在ProviderManagerauthenticate方法中,轮训成员变量List<AuthenticationProvider> providers。该providers中如果有一个

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

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

    4、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);
        }

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

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

    3、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认证配置(一)

  • 相关阅读:
    【每日英语】
    【百宝箱】CLion: Cound not load cache
    C# WPF:这次把文件拖出去!
    C# WPF:快把文件从桌面拖进我的窗体来!
    两个List< string>比较是否相同的N种方法,你用过哪种?
    分享套接字数据包序列化与反序列化方法
    如何从含有占位符的字符串生成一个ReactNode数组
    vscode 插件配置指北
    第十一周总结
    机场&代理商-关系图
  • 原文地址:https://www.cnblogs.com/xuwenjin/p/9565356.html
Copyright © 2020-2023  润新知