Abp Core 添加短信验证码登录(动态密码登录)

现目前我国网站的已经很少使用电子邮箱了,基本上都是手机号作为账号,有时候粗心的用户会忘记密码,网站就提供了更方便的短信验证码登录.使用短信验证码来替代密码,本小白也想实现这个功能,但是Abp并没有提供这样的方法,于是经过不懈努力,终于搞定了,废话不多说下面上代码.
1.添加通过手机号查询用户方法
在Core\Authorization\Users\UserStore.cs

/// <summary>
    /// 通过手机号查询用户
    /// </summary>
    /// <param name="phoneNumber">手机号</param>
    /// <returns></returns>
    public virtual async Task<User> FindByPhoneNumberAsync(string phoneNumber)
    {
        return await _userRepository.FirstOrDefaultAsync(model => model.PhoneNumber == phoneNumber);
    }
    /// <summary>
    /// 通过手机号查询用户
    /// </summary>
    /// <param name="tenantId">租户id</param>
    /// <param name="phoneNumber">手机号</param>
    /// <returns></returns>
    [UnitOfWork]
    public virtual async Task<User> FindByPhoneNumberAsync(int? tenantId, string phoneNumber)
    {
        using (_unitOfWorkManager.Current.SetTenantId(tenantId))
        {
            return await FindByPhoneNumberAsync(phoneNumber);
        }
    }

2.添加验证码效验方法
在Core\Authorization\Users\UserManager.cs

/// <summary>
    /// 检查验证码
    /// </summary>
    /// <param name="user">用户</param>
    /// <param name="captcha">验证码</param>
    /// <returns></returns>
    public virtual async Task<bool> CheckCaptcha(User user, int captcha)
    {
        var entity = await _smsCaptchaRepository.FirstOrDefaultAsync(model => model.PhoneNumer == user.PhoneNumber);
        if (entity.Captcha == captcha)
            return true;
        return false;
    }

3.添加手机验证码登录功能
在Core\Authorization\LoginManager.cs

手机验证码登陆

/// <summary>
    /// 手机验证码登陆
    /// </summary>
    /// <param name="phoneNumber">手机号</param>
    /// <param name="captcha">验证码</param>
    /// <param name="tenantName">租户名</param>
    /// <param name="shouldLockout">是否锁定</param>
    /// <returns></returns>
    public virtual async Task<AbpLoginResult<Tenant, User>> LoginByMobileAsync(string phoneNumber, int captcha,
        string tenantName = null, bool shouldLockout = true)
    {
        var result = await LoginByMobileAsyncInternal(phoneNumber, captcha, tenantName, shouldLockout);
        await SaveLoginAttempt(result, tenantName, result.User.UserName);
        return result;
    }

手机验证码登陆内部方法

/// <summary>
    /// 手机验证码登陆内部方法
    /// </summary>
    /// <param name="phoneNumber">手机号</param>
    /// <param name="captcha">验证码</param>
    /// <param name="tenantName">租户名</param>
    /// <param name="shouldLockout">是否锁定</param>
    /// <returns></returns>
    protected virtual async Task<AbpLoginResult<Tenant, User>> LoginByMobileAsyncInternal(string phoneNumber,
        int captcha, string tenantName, bool shouldLockout)
    {
        if (phoneNumber.IsNullOrEmpty())
        {
            Logger.Error("手机号不能为空");
            throw new UserFriendlyException("手机号不能为空");
        }
        //获取和检查租户
        Tenant tenant = null;
        //设置当前租户id为空
        using (_unitOfWorkManager.Current.SetTenantId(null))
        {
            //如果关闭租户就获取默认租户
            if (!_multiTenancyConfig.IsEnabled)
            {
                //获取默认租户
                tenant = await GetDefaultTenantAsync();
            }
            //租户id是否为空
            else if (!string.IsNullOrWhiteSpace(tenantName))
            {
                //查询租户信息
                tenant = await _tenantRepository.FirstOrDefaultAsync(model => model.TenancyName == tenantName);
                if (tenant == null)
                    //返回无效租户
                    return new AbpLoginResult<Tenant, User>(AbpLoginResultType.InvalidTenancyName);
                if (!tenant.IsActive)
                    //返回租户未启用
                    return new AbpLoginResult<Tenant, User>(AbpLoginResultType.TenantIsNotActive, tenant);
            }
        }
        //设置租户id
        var tenantId = tenant == null ? (int?)null : tenant.Id;
        //设置当前租户id
        using (_unitOfWorkManager.Current.SetTenantId(tenantId))
        {
            //初始化选项
            await _userManager.InitializeOptionsAsync(tenantId);
            var loggedInFromExternalSource = false; 
            //通过手机号查询用户
            var user = await _userStore.FindByPhoneNumberAsync(tenantId, phoneNumber);
            if (user == null)
                //返回无效用户
                return new AbpLoginResult<Tenant, User>(AbpLoginResultType.InvalidUserNameOrEmailAddress, tenant);
            //用户是否被锁定
            if (await _userManager.IsLockedOutAsync(user))
                //返回锁定用户
                return new AbpLoginResult<Tenant, User>(AbpLoginResultType.LockedOut, tenant, user);
            if (!loggedInFromExternalSource)
            {
                if (!await _userManager.CheckCaptcha(user, captcha))
                {
                    //是否锁定
                    if (shouldLockout)
                    {
                        //锁定账户
                        if (await TryLockOutAsync(tenantId, user.Id))
                            //返回锁定账户
                            return new AbpLoginResult<Tenant, User>(AbpLoginResultType.LockedOut, tenant, user);
                    }
                        //返回无效密码
                        return new AbpLoginResult<Tenant, User>(AbpLoginResultType.InvalidPassword, tenant, user);
                }
                    //重置登陆失败次数
                    await UserManager.ResetAccessFailedCountAsync(user);
            }
            return await CreateLoginByMobileResultAsync(user, tenant);
        }
    }

创建手机号登陆结果

/// <summary>
    /// 创建手机号登陆结果
    /// </summary>
    /// <param name="user">用户</param>
    /// <param name="tenant">租户</param>
    /// <returns></returns>
    protected virtual async Task<AbpLoginResult<Tenant, User>> CreateLoginByMobileResultAsync(User user,
        Tenant tenant = null)
    {
        //用户未激活
        if (!user.IsActive)
            //返回用户未激活
            return new AbpLoginResult<Tenant, User>(AbpLoginResultType.UserIsNotActive, tenant, user);
        //用户手机号未认证
        if (user.IsPhoneNumberConfirmed)
            //返回用户手机号未认证
            return new AbpLoginResult<Tenant, User>(AbpLoginResultType.UserPhoneNumberIsNotConfirmed, tenant, user);
        user.LastLoginTime = Clock.Now;
        //更新用户
        await _userManager.UpdateAsync(user);
        await _unitOfWorkManager.Current.SaveChangesAsync();
        var principal = await _claimsPrincipalFactory.CreateAsync(user);
        return new AbpLoginResult<Tenant, User>(
            tenant,
            user,
            principal.Identity as ClaimsIdentity
        );
    }

添加webApi
在web.core\Controllers\TokenAuthController.cs
通过手机验证码授权

/// <summary>
    /// 通过手机验证码授权
    /// </summary>
    /// <param name="model">手机验证码Dto</param>
    /// <returns></returns>
    [HttpPost]
    public async Task<AuthenticateResultModel> AuthenticateByPhoneCaptcha([FromBody] AuthenticateByPhoneCaptchaModel model)
    {
        var loginResult = await GetLoginResultByPhoneCaptchaAsync(
            model.PhoneNumber,
            model.Captcha,
            GetTenancyNameOrNull()
        );
        var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
        return new AuthenticateResultModel
        {
            AccessToken = accessToken,
            EncryptedAccessToken = GetEncrpyedAccessToken(accessToken),
            ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
            UserId = loginResult.User.Id
        };
    }

** 获取登陆结果通过手机验证码**

/// <summary>
    /// 获取登陆结果通过手机验证码
    /// </summary>
    /// <param name="phoneNumber">手机号</param>
    /// <param name="captcha">验证码</param>
    /// <param name="tenancyName">租户名</param>
    /// <returns></returns>
    private async Task<AbpLoginResult<Tenant, User>> GetLoginResultByPhoneCaptchaAsync(string phoneNumber, int captcha, string tenancyName)
    {
        var loginResult = await _logInManager.LoginByMobileAsync(phoneNumber, captcha, tenancyName);
        switch (loginResult.Result)
        {
            case AbpLoginResultType.Success:
                return loginResult;
            default:
                throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, loginResult.User.UserName, tenancyName);
        }
    }

至此大工告成,还有手机号登录,将在下一篇文章讲解.代码上有详细注解