/** 重写父类方法,当登录失败次数大于allowLoginNum(允许登录次)时,将显示验证码 */
  @Override
  protected boolean onLoginFailure(
      AuthenticationToken token,
      AuthenticationException e,
      ServletRequest request,
      ServletResponse response) {
    if (e instanceof CaptchaValidationException) {
      request.setAttribute(KEY_AUTH_CAPTCHA_REQUIRED, Boolean.TRUE);
    } else if (e instanceof IncorrectCredentialsException) {
      // 消息友好提示
      e = new IncorrectCredentialsException("登录账号或密码不正确");
      // 失败记录
      SourceUsernamePasswordToken sourceUsernamePasswordToken = (SourceUsernamePasswordToken) token;
      User authAccount =
          userService.findByAuthTypeAndAuthUid(
              User.AuthTypeEnum.SYS, sourceUsernamePasswordToken.getUsername());
      if (authAccount != null) {
        authAccount.setLogonTimes(authAccount.getLogonTimes() + 1);
        authAccount.setLastLogonFailureTime(DateUtils.currentDate());
        authAccount.setLogonFailureTimes(authAccount.getLogonFailureTimes() + 1);
        userService.save(authAccount);

        // 达到验证失败次数限制,传递标志属性,登录界面显示验证码输入
        if (authAccount.getLogonFailureTimes() > LOGON_FAILURE_LIMIT) {
          request.setAttribute(KEY_AUTH_CAPTCHA_REQUIRED, Boolean.TRUE);
        }
      }
    }
    return super.onLoginFailure(token, e, request, response);
  }
  @Override
  protected boolean executeLogin(ServletRequest request, ServletResponse response)
      throws Exception {
    SourceUsernamePasswordToken token =
        (SourceUsernamePasswordToken) createToken(request, response);
    try {
      String username = getUsername(request);
      // 写入登录账号名称用于回显
      request.setAttribute(KEY_AUTH_USERNAME_VALUE, username);

      User authAccount = userService.findByAuthTypeAndAuthUid(User.AuthTypeEnum.SYS, username);
      if (authAccount != null) {

        // 失败LOGON_FAILURE_LIMIT次,强制要求验证码验证
        if (authAccount.getLogonFailureTimes() > LOGON_FAILURE_LIMIT) {
          String captcha = request.getParameter(captchaParam);
          if (StringUtils.isBlank(captcha)
              || !ImageCaptchaServlet.validateResponse((HttpServletRequest) request, captcha)) {
            throw new CaptchaValidationException("验证码不正确");
          }
        }

        Subject subject = getSubject(request, response);
        subject.login(token);
        return onLoginSuccess(token, subject, request, response);
      } else {
        return onLoginFailure(token, new UnknownAccountException("登录账号或密码不正确"), request, response);
      }
    } catch (AuthenticationException e) {
      return onLoginFailure(token, e, request, response);
    }
  }
  /** 重写父类方法,当登录成功后,重置失败标志 */
  @Override
  protected boolean onLoginSuccess(
      AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response)
      throws Exception {
    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;

    SourceUsernamePasswordToken sourceUsernamePasswordToken = (SourceUsernamePasswordToken) token;
    User authAccount =
        userService.findByAuthTypeAndAuthUid(
            User.AuthTypeEnum.SYS, sourceUsernamePasswordToken.getUsername());
    Date now = DateUtils.currentDate();

    // 更新Access Token,并设置半年后过期
    if (StringUtils.isBlank(authAccount.getAccessToken())
        || authAccount.getAccessTokenExpireTime().before(now)) {
      authAccount.setAccessToken(UUID.randomUUID().toString());
      authAccount.setAccessTokenExpireTime(
          new DateTime(DateUtils.currentDate()).plusMonths(6).toDate());
      userService.save(authAccount);
    }

    // 写入登入记录信息
    UserLogonLog userLogonLog = new UserLogonLog();
    userLogonLog.setLogonTime(DateUtils.currentDate());
    userLogonLog.setLogonYearMonthDay(DateUtils.formatDate(userLogonLog.getLogoutTime()));
    userLogonLog.setRemoteAddr(httpServletRequest.getRemoteAddr());
    userLogonLog.setRemoteHost(httpServletRequest.getRemoteHost());
    userLogonLog.setRemotePort(httpServletRequest.getRemotePort());
    userLogonLog.setLocalAddr(httpServletRequest.getLocalAddr());
    userLogonLog.setLocalName(httpServletRequest.getLocalName());
    userLogonLog.setLocalPort(httpServletRequest.getLocalPort());
    userLogonLog.setServerIP(IPAddrFetcher.getGuessUniqueIP());
    userLogonLog.setHttpSessionId(httpServletRequest.getSession().getId());
    userLogonLog.setUserAgent(httpServletRequest.getHeader("User-Agent"));
    userLogonLog.setXforwardFor(IPAddrFetcher.getRemoteIpAddress(httpServletRequest));
    userLogonLog.setAuthType(authAccount.getAuthType());
    userLogonLog.setAuthUid(authAccount.getAuthUid());
    userLogonLog.setAuthGuid(authAccount.getAuthGuid());
    userService.userLogonLog(authAccount, userLogonLog);

    if (isMobileAppAccess(request)) {
      return true;
    } else {
      // 根据不同登录类型转向不同成功界面
      AuthUserDetails authUserDetails = AuthContextHolder.getAuthUserDetails();

      // 判断密码是否已到期,如果是则转向密码修改界面
      Date credentialsExpireTime = authAccount.getCredentialsExpireTime();
      if (credentialsExpireTime != null && credentialsExpireTime.before(DateUtils.currentDate())) {
        httpServletResponse.sendRedirect(
            httpServletRequest.getContextPath()
                + authUserDetails.getUrlPrefixBySource()
                + "/profile/credentials-expire");
        return false;
      }

      // 如果是强制转向指定successUrl则清空SavedRequest
      if (forceSuccessUrl) {
        WebUtils.getAndClearSavedRequest(httpServletRequest);
      }

      return super.onLoginSuccess(token, subject, request, httpServletResponse);
    }
  }