AuthenticationManager 认证原理分析 (Spring Security)

AuthenticationManager 认证原理分析

说明

AuthenticationManager 是 Spring Security提供的一套认证服务
类运行流程图:
在这里插入图片描述

概述

上述是AuthenticationManager 认证流程的类流程,现在咱们逐个分析这些类
1.SysLoginController:登录控制器
2.SysLoginService 登录业务类
3. WebSecurityConfigurerAdapter:网络安全配置器适配器 AuthenticationManagerDelegator(内部类):认证管理器委托人
4.ProviderManager :提供者管理器
5.AbstractUserDetailsAuthenticationProvider: 抽象用户详细信息身份验证提供程序
6.DaoAuthenticationProvider:数据层认证提供者
7.UserDetailsServiceImpl:用户详细信息服务实现

原理分析:根据类运行流程图,我们可以直接得到实现原理为,登录控制器1.发送登录请求2.执行登录业务类同时3.执行 网络安全配置器适配器中的适配一个认证管理器委托人4.再通过提供者管理器5.调用 抽象用户详细信息身份验证提供程序6.再调用数据库层认证提供者再7.调用用户详细信息服务实现

代码执行流程

1.首先进入自定义的业务验证的方法中(SysLoginController类)

/**
 * 登录方法
 * 
 * @param loginBody 登录信息
 * @return 结果
 */
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody)
{
    AjaxResult ajax = AjaxResult.success();
    // 生成令牌
    String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
            loginBody.getUuid());
    ajax.put(Constants.TOKEN, token);
    return ajax;
}

2.进入该函数,执行authenticate函数(SysLoginService类)

public String login(String username, String password, String code, String uuid)
{
    boolean captchaOnOff = configService.selectCaptchaOnOff();
    // 验证码开关
    if (captchaOnOff)
    {
        //验证验证码是否正确
        validateCaptcha(username, code, uuid);
    }
    // 用户验证
    Authentication authentication = null;
    try
    {
        // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
★        authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
    }
    catch (Exception e)
    {
        if (e instanceof BadCredentialsException)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        else
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
            throw new ServiceException(e.getMessage());
        }
    }
    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
    LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    recordLoginInfo(loginUser.getUserId());
    // 生成token
    return tokenService.createToken(loginUser);
}

3.调用authenticate方法(AuthenticationManagerDelegator类(WebSecurityConfigurerAdapter的内部类))

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    if (this.delegate != null) {return this.delegate.authenticate(authentication);
    } else {
        synchronized(this.delegateMonitor) {
            if (this.delegate == null) {
                this.delegate = (AuthenticationManager)this.delegateBuilder.getObject();
                this.delegateBuilder = null;
            }
        }return this.delegate.authenticate(authentication);
    }

4.匹配对应的认证器,继续调用认证器中的authenticate方法(ProviderManager类)ProviderManager类中提供了,认证管理器集合,和父认证管理器来处理认证。
在这里插入图片描述

private static final Log logger = LogFactory.getLog(ProviderManager.class);

private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
//认证管理器集合private List<AuthenticationProvider> providers = Collections.emptyList();

protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

//父认证管理器private AuthenticationManager parent;

private boolean eraseCredentialsAfterAuthentication = true;

@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();
   for (AuthenticationProvider provider : getProviders()) {
      if (!provider.supports(toTest)) {
         continue;
      }
      if (logger.isTraceEnabled()) {
         logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
               provider.getClass().getSimpleName(), ++currentPosition, size));
      }
      try {
          //调用对应的认证管理器
★         result = provider.authenticate(authentication);
         if (result != null) {
            copyDetails(authentication, result);
            break;
         }
      }
      catch (AccountStatusException | InternalAuthenticationServiceException ex) {
         prepareException(ex, authentication);
         // SEC-546: Avoid polling additional providers if auth failure is due to
         // invalid account status
         throw ex;
      }
      catch (AuthenticationException ex) {
         lastException = ex;
      }
   }
   if (result == null && this.parent != null) {
      // Allow the parent to try.
      try {
            //调用对应的认证管理器
★         parentResult = this.parent.authenticate(authentication);
         result = parentResult;
      }
      catch (ProviderNotFoundException ex) {
         // ignore as we will throw below if no other exception occurred prior to
         // calling parent and the parent
         // may throw ProviderNotFound even though a provider in the child already
         // handled the request
      }
      catch (AuthenticationException ex) {
         parentException = ex;
         lastException = ex;
      }
   }
   if (result != null) {
      if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
         // Authentication is complete. Remove credentials and other secret data
         // from authentication
         ((CredentialsContainer) result).eraseCredentials();
      }
      // If the parent AuthenticationManager was attempted and successful then it
      // will publish an AuthenticationSuccessEvent
      // This check prevents a duplicate AuthenticationSuccessEvent if the parent
      // AuthenticationManager already published it
      if (parentResult == null) {
         this.eventPublisher.publishAuthenticationSuccess(result);
      }

      return result;
   }

   // Parent was null, or didn't authenticate (or throw an exception).
   if (lastException == null) {
      lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
            new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
   }
   // If the parent AuthenticationManager was attempted and failed then it will
   // publish an AbstractAuthenticationFailureEvent
   // This check prevents a duplicate AbstractAuthenticationFailureEvent if the
   // parent AuthenticationManager already published it
   if (parentException == null) {
      prepareException(lastException, authentication);
   }
   throw lastException;
}

5.调用认证器中的authenticate认证方法(AbstractUserDetailsAuthenticationProvider 类)
在这里插入图片描述

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
   Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
         () -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
               "Only UsernamePasswordAuthenticationToken is supported"));
   String username = determineUsername(authentication);
   boolean cacheWasUsed = true;
   UserDetails user = this.userCache.getUserFromCache(username);
   if (user == null) {
      cacheWasUsed = false;
      try {
          //验证用户详细信息
★        user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
      }
      catch (UsernameNotFoundException ex) {
         this.logger.debug("Failed to find user '" + username + "'");
         if (!this.hideUserNotFoundExceptions) {
            throw ex;
         }
         throw new BadCredentialsException(this.messages
               .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
      }
      Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
   }
   try {
      this.preAuthenticationChecks.check(user);
      additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
   }
   catch (AuthenticationException ex) {
      if (!cacheWasUsed) {
         throw ex;
      }
      // There was a problem, so try again after checking
      // we're using latest data (i.e. not from the cache)
      cacheWasUsed = false;
      user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
      this.preAuthenticationChecks.check(user);
      additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
   }
   this.postAuthenticationChecks.check(user);
   if (!cacheWasUsed) {
      this.userCache.putUserInCache(user);
   }
   Object principalToReturn = user;
   if (this.forcePrincipalAsString) {
      principalToReturn = user.getUsername();
   }
   return createSuccessAuthentication(principalToReturn, authentication, user);
}

6.加载用户名认证函数(DaoAuthenticationProvider类)
在这里插入图片描述

@Override
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);
   }
}

7.执行认证函数(UserDetailsServiceImpl类 实现UserDetailsService)

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{SysUser user = userService.selectUserByUserName(username);
    if (StringUtils.isNull(user))
    {
        log.info("登录用户:{} 不存在.", username);
        throw new ServiceException("登录用户:" + username + " 不存在");
    }
    else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
    {
        log.info("登录用户:{} 已被删除.", username);
        throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
    }
    else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
    {
        log.info("登录用户:{} 已被停用.", username);
        throw new ServiceException("对不起,您的账号:" + username + " 已停用");
    }

    return createLoginUser(user);
}

7.1执行验证用户名密码函数(DaoAuthenticationProvider类)

@Override
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
      UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
   if (authentication.getCredentials() == null) {
      this.logger.debug("Failed to authenticate since no credentials provided");
      throw new BadCredentialsException(this.messages
            .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
   }
   String presentedPassword = authentication.getCredentials().toString();if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
      this.logger.debug("Failed to authenticate since password does not match stored value");
      throw new BadCredentialsException(this.messages
            .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
   }
}

认证函数执行流程图

在这里插入图片描述

总结

AuthenticationManager 主要就是ProviderManager中提供一个认证管理器集合ist providers和父认证管理器 AuthenticationManager parent,匹配对应的认证管理器,然后再调用dao层用户信息匹配(用户名,密码),达到认证用户信息的功能

如果没有批评,赞美将毫无意义,欢迎指正批评。

路漫漫其修远兮,吾将上下而求索

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值