首页 > 其他分享 >用Abp实现双因素认证(Two-Factor Authentication, 2FA)登录(一):认证模块

用Abp实现双因素认证(Two-Factor Authentication, 2FA)登录(一):认证模块

时间:2023-04-07 22:12:16浏览次数:47  
标签:TwoFactorLogin string Two 认证 Abp UserManagement public

@

目录
在之前的博文 用Abp实现短信验证码免密登录(一):短信校验模块 一文中,我们实现了用户验证码校验模块,今天来拓展这个模块,使Abp用户系统支持双因素认证(Two-Factor Authentication)功能。

双因素认证(Two-Factor Authentication,简称 2FA)是使用两个或多个因素的任意组合来验证用户身份,例如用户提供密码后,还要提供短消息发送的验证码,以证明用户确实拥有该手机。

国内大多数网站在登录屏正常登录后,检查是否有必要进行二次验证,如果有必要则进入二阶段验证屏,如下图:

在这里插入图片描述

在这里插入图片描述

接下来就来实践这个小项目

本示例基于之前的博文内容,你需要登录并绑定正确的手机号,才能使用双因素认证。示例代码已经放在了GitHub上:Github:matoapp-samples

原理

查看Abp源码,Abp帮我们定义了几个Setting,用于配置双因素认证的相关功能。确保在数据库中将Abp.Zero.UserManagement.TwoFactorLogin.IsEnabled打开。

public static class TwoFactorLogin
{
    /// <summary>
    /// "Abp.Zero.UserManagement.TwoFactorLogin.IsEnabled".
    /// </summary>
    public const string IsEnabled = "Abp.Zero.UserManagement.TwoFactorLogin.IsEnabled";

    /// <summary>
    /// "Abp.Zero.UserManagement.TwoFactorLogin.IsEmailProviderEnabled".
    /// </summary>
    public const string IsEmailProviderEnabled = "Abp.Zero.UserManagement.TwoFactorLogin.IsEmailProviderEnabled";

    /// <summary>
    /// "Abp.Zero.UserManagement.TwoFactorLogin.IsSmsProviderEnabled".
    /// </summary>
    public const string IsSmsProviderEnabled = "Abp.Zero.UserManagement.TwoFactorLogin.IsSmsProviderEnabled";

...
}

在AbpUserManager的GetValidTwoFactorProvidersAsync方法中

Abp.Zero.UserManagement.TwoFactorLogin.IsSmsProviderEnabled开启后将添加“Phone”到Provider中,将启用短信验证方式。

Abp.Zero.UserManagement.TwoFactorLogin.IsEmailProviderEnabled开启后将添加“Email”到Provider中,将启用邮箱验证方式。

var isEmailProviderEnabled = await IsTrueAsync(
    AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEmailProviderEnabled,
    user.TenantId
);

if (provider == "Email" && !isEmailProviderEnabled)
{
    continue;
}

var isSmsProviderEnabled = await IsTrueAsync(
    AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsSmsProviderEnabled,
    user.TenantId
);

if (provider == "Phone" && !isSmsProviderEnabled)
{
    continue;
}

在迁移中添加双因素认证的配置项

//双因素认证
AddSettingIfNotExists(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEnabled, "true", tenantId);
AddSettingIfNotExists(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsSmsProviderEnabled, "true", tenantId);
AddSettingIfNotExists(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEmailProviderEnabled, "true", tenantId);

将默认User的IsTwoFactorEnabled字段设为true

public User()
{
    this.IsTwoFactorEnabled= true;
}

用户验证码校验模块

使用AbpBoilerplate.Sms作为短信服务库。

之前定义了DomainService接口,已经实现了验证码的发送、验证码校验、解绑手机号、绑定手机号

这4个功能,通过定义用途(purpose)字段以校验区分短信模板

public interface ICaptchaManager
{
    Task BindAsync(string token);
    Task UnbindAsync(string token);
    Task SendCaptchaAsync(long userId, string phoneNumber, string purpose);
    Task<bool> VerifyCaptchaAsync(string token, string purpose = "IDENTITY_VERIFICATION");
}

添加一个用于双因素认证的purpose,在CaptchaPurpose枚举类型中添加TWO_FACTOR_AUTHORIZATION

public const string TWO_FACTOR_AUTHORIZATION = "TWO_FACTOR_AUTHORIZATION";

在SMS服务商管理端后台申请一个短信模板,用于双因素认证。

在这里插入图片描述

打开短信验证码的领域服务类SmsCaptchaManager, 添加TWO_FACTOR_AUTHORIZATION对应短信模板的编号

public async Task SendCaptchaAsync(long userId, string phoneNumber, string purpose)
{
    var captcha = CommonHelper.GetRandomCaptchaNumber();
    var model = new SendSmsRequest();
    model.PhoneNumbers = new string[] { phoneNumber };
    model.SignName = "MatoApp";
    model.TemplateCode = purpose switch
    {
        CaptchaPurpose.BIND_PHONENUMBER => "SMS_255330989",
        CaptchaPurpose.UNBIND_PHONENUMBER => "SMS_255330923",
        CaptchaPurpose.LOGIN => "SMS_255330901",
        CaptchaPurpose.IDENTITY_VERIFICATION => "SMS_255330974"
        CaptchaPurpose.TWO_FACTOR_AUTHORIZATION => "SMS_1587660"    //添加双因素认证对应短信模板的编号
    };

    ...
}

双因素认证模块

创建双因素认证领域服务类TwoFactorAuthorizationManager。

创建方法IsTwoFactorAuthRequiredAsync,返回登录用户是否需要双因素认证,若未开启TwoFactorLogin.IsEnabled、用户未开启双因素认证,或没有添加验证提供者,则跳过双因素认证。

public async Task<bool> IsTwoFactorAuthRequiredAsync(AbpLoginResult<Tenant, User> loginResult)
{
    if (!await settingManager.GetSettingValueAsync<bool>(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEnabled))
    {
        return false;
    }

    if (!loginResult.User.IsTwoFactorEnabled)
    {
        return false;
    }
    if ((await _userManager.GetValidTwoFactorProvidersAsync(loginResult.User)).Count <= 0)
    {
        return false;
    }
    return true;
}

创建TwoFactorAuthenticateAsync,此方法根据回传的provider和token值校验用户是否通过双因素认证。

public async Task TwoFactorAuthenticateAsync(User user, string token, string provider)
{
    if (provider == "Email")
    {
        var isValidate = await emailCaptchaManager.VerifyCaptchaAsync(token, CaptchaPurpose.TWO_FACTOR_AUTHORIZATION);
        if (!isValidate)
        {
            throw new UserFriendlyException("验证码错误");
        }
    }

    else if (provider == "Phone")
    {
        var isValidate = await smsCaptchaManager.VerifyCaptchaAsync(token, CaptchaPurpose.TWO_FACTOR_AUTHORIZATION);
        if (!isValidate)
        {
            throw new UserFriendlyException("验证码错误");
        }
    }
    else
    {
        throw new UserFriendlyException("验证码提供者错误");
    }

    
}

创建SendCaptchaAsync,此方用于发送验证码。

public async Task SendCaptchaAsync(long userId, string Provider)
{
    var user = await _userManager.FindByIdAsync(userId.ToString());
    if (user == null)
    {
        throw new UserFriendlyException("找不到用户");

    }

    if (Provider == "Email")
    {
        if (!user.IsEmailConfirmed)
        {
            throw new UserFriendlyException("未绑定邮箱");
        }
        await emailCaptchaManager.SendCaptchaAsync(user.Id, user.EmailAddress, CaptchaPurpose.TWO_FACTOR_AUTHORIZATION);
    }
    else if (Provider == "Phone")
    {
        if (!user.IsPhoneNumberConfirmed)
        {
            throw new UserFriendlyException("未绑定手机号");
        }
        await smsCaptchaManager.SendCaptchaAsync(user.Id, user.PhoneNumber, CaptchaPurpose.TWO_FACTOR_AUTHORIZATION);
    }
    else
    {
        throw new UserFriendlyException("验证提供者错误");
    }
}

改写登录

接下来将双因素认证逻辑添加到登录流程中。

在web.core项目中,
添加类SendTwoFactorAuthenticateCaptchaModel,发送验证码时将一阶段返回的userId和选择验证方式的provider传入

public class SendTwoFactorAuthenticateCaptchaModel
{
    [Range(1, long.MaxValue)]
    public long UserId { get; set; }

    [Required]
    public string Provider { get; set; }
}

将验证码Token,和验证码提供者Provider的定义添加到AuthenticateModel中

public string TwoFactorAuthenticationToken { get; set; }

public string TwoFactorAuthenticationProvider { get; set; }

将提供者列表TwoFactorAuthenticationProviders,和是否需要双因素认证RequiresTwoFactorAuthenticate的定义添加到AuthenticateResultModel中

public bool RequiresTwoFactorAuthenticate { get; set; }

public IList<string> TwoFactorAuthenticationProviders { get; set; }

打开TokenAuthController,注入UserManager和TwoFactorAuthorizationManager服务对象

添加终节点SendTwoFactorAuthenticateCaptcha,用于前端调用发送验证码

[HttpPost]
public async Task SendTwoFactorAuthenticateCaptcha([FromBody] SendTwoFactorAuthenticateCaptchaModel model)
{
    await twoFactorAuthorizationManager.SendCaptchaAsync(model.UserId, model.Provider);
}

改写Authenticate方法如下:

[HttpPost]
public async Task<AuthenticateResultModel> Authenticate([FromBody] AuthenticateModel model)
{
    //用户名密码校验
    var loginResult = await GetLoginResultAsync(
        model.UserNameOrEmailAddress,
        model.Password,
        GetTenancyNameOrNull()
    );

    await userManager.InitializeOptionsAsync(loginResult.Tenant?.Id);

    //判断是否需要双因素认证
    if (await twoFactorAuthorizationManager.IsTwoFactorAuthRequiredAsync(loginResult))
    {
        //判断是否一阶段
        if (string.IsNullOrEmpty(model.TwoFactorAuthenticationToken))
        {
            //一阶登录完成,返回结果,等待二阶段登录
            return new AuthenticateResultModel
            {
                RequiresTwoFactorAuthenticate = true,
                UserId = loginResult.User.Id,
                TwoFactorAuthenticationProviders = await userManager.GetValidTwoFactorProvidersAsync(loginResult.User),

            };
        }
        //二阶段,双因素认证校验
        else
        {
            await twoFactorAuthorizationManager.TwoFactorAuthenticateAsync(loginResult.User, model.TwoFactorAuthenticationToken, model.TwoFactorAuthenticationProvider);
        }
    }

    //二阶段完成,返回最终登录结果
    var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
    return new AuthenticateResultModel
    {
        AccessToken = accessToken,
        EncryptedAccessToken = GetEncryptedAccessToken(accessToken),
        ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
        UserId = loginResult.User.Id,
    };
}

在这里插入图片描述

至此,双因素认证的后端逻辑已经完成,接下来我们将补充“记住”功能,实现一段时间内免验证。

标签:TwoFactorLogin,string,Two,认证,Abp,UserManagement,public
From: https://www.cnblogs.com/jevonsflash/p/17297520.html

相关文章

  • 手动配置resolv.conf 文件,不被NetworkManager修改
    /etc/resolv.conf默认情况下,RedHatEnterpriseLinux(RHEL)8上的NetworkManager使用来自活动NetworkManager连接配置文件的DNS设置 动态更新文件。但是,您可以禁用此行为并在/etc/resolv.conf.笔记或者,如果您需要DNS服务器的特定顺序/etc/resolv.conf,请参阅配置DNS服......
  • 21An efficient message-authentication scheme based on edge computing for vehicul
    ......
  • TOTAL:AWS 认证解决方案架构师助理 (SAA-C03)
    TOTAL:AWS认证解决方案架构师助理(SAA-C03)超值:通过AWSCertifiedSolutionsArchitect–AssociateSAA-C03考试+AWSEssentials和2次免费练习考试课程英文名:TOTALAWSCertifiedSolutionsArchitectAssociate(SAA-C03)此视频教程共15.9GB,中英双语字幕,画质清晰无......
  • https请求ssl认证失败
    问题描述:程序需使用第三方进行开票,第三方服务提供https的post请求接口,并提供基于pfx证书的安全认证。原始服务基于.netcore3.1,dockerfile基础镜像是mcr.microsoft.com/dotnet/core/aspnet:3.1-bionic,版本迭代时进行了sdk升级为.net6,基础镜像是mcr.microsoft.com/dotnet/aspnet:6.......
  • HTTPS双向认证【转】
    背景在三方接口对接中,偶尔会遇到需要传递证书的情况,这种方式其实是在SSL握手过程中会同时验证客户端和服务器的身份,这就是我们常说的 双向认证。双向认证需要服务器和客户端提供身份认证,只能是服务器允许的客户方能访问,安全性相对于要高一些。下面老黄用几个小例子来演示一下......
  • JWT 实现登录认证 + Token 自动续期方案
    要实现认证功能,很容易就会想到JWT或者session,但是两者有啥区别?各自的优缺点?应该Pick谁?夺命三连区别基于session和基于JWT的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而JWT是保存在客户端的认证流程基于session的认证流程用户在浏览器中输入用户名和密码,服务......
  • Graph Neural Networks for Link Prediction with Subgraph Sketching
    目录概符号说明必要的定义MotivationELPH代码ChamberlainB.P.,ShirobokovS.,RossiE.,FrascaF.,MarkovichT.,HammerlaN.,BronsteinM.M.HansmireM.Graphneuralnetworksforlinkpredictionwithsubgraphsketching.InInternationalConferenceonLearnin......
  • Redis 在身份认证中的应用
    1.Redis在Session共享问题中的应用传统Session-Cookeis身份认证方法中,一个Session只保存在一台服务器上,适合域单体应用。随着项目规模的增加,项目的架构也不断向微服务分布式集群演进,传统的Session-Cookie方式在集群环境下就不能很好的工作了,这时就产生了Session共......
  • Abp自定义模块种子数据
    模块的初始化或者系统的基本运行需要一些基础数据,可以利用ABP提供的种子数据基础上设置进行数据播种。自定义模块自定义模块可以定义自己的DataSeeder,例如数据字典:publicinterfaceIDataDictionaryDataSeeder{TaskSeedAsync(stringname,stringvalue);}定义......
  • 解决Abp设置DefaultLanguage默认语言不生效的问题
    @目录现象原因分析解决问题现象默认地,Abp的语言提供程序将返回的CultureInfo为En,在一些默认实现的接口(比如/api/TokenAuth/Authenticate)返回的错误信息是英文目标是改成简体中文显示,但是即便我们在AbpSettings表中加入了DefaultLanguage为"zh-Hans"AddSettingIfNotExists(L......