TFA(双因素认证,Two-Factor Authentication)验证

1.服务器为每个用户生成唯一的32位密钥,通过二维码安全传递给认证器应用

/// <summary>
/// 生成TFA二维码
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
/// <exception cref="CustomException"></exception>
public async Task<string> GeneratingVerifyQRCode(long userId)
{
    var user = await _db.Queryable<User>().Where(u => u.Id == userId).FirstOrDefaultAsync();
    if (user == null)
        throw new CustomException(ErrorCode.LoginAccountIsNotExist);
    string accountTitle = $"TestBase:{user.Account}";
    string code = user.Account + "|" + user.TFASeed;
    string key = EncryptCodeHelper.MD5Encrypt(code);
    var qrCode = TFAHelper.GeneratingVerifyQRCode(accountTitle, key, 300, 300);
    return qrCode;
}

 EncryptCodeHelper.MD5Encrypt

public static string MD5Encrypt(string data)
{
    MD5 mD5 = MD5.Create();
    byte[] s = mD5.ComputeHash(Encoding.UTF8.GetBytes(data));
    return BitConverter.ToString(s).Replace("-", "");
}

TFAHelper.GeneratingVerifyQRCode

 /// <summary>
 /// 生成TFA二维码
 /// </summary>
 /// <param name="accountTitle"></param>
 /// <param name="accountSecretKey"></param>
 /// <param name="qrCodeWidth">二维码宽</param>
 /// <param name="qrCodeHeight">二维码高</param>
 /// <returns></returns>
 public static string GeneratingVerifyQRCode(string accountTitle, string accountSecretKey, int qrCodeWidth, int qrCodeHeight)
 {
     TwoFactorAuthenticator twoFactor = new TwoFactorAuthenticator();
     var setupInfo = twoFactor.GenerateSetupCode(accountTitle, accountSecretKey, qrCodeWidth, qrCodeHeight);

     #region 二维码生成
     string content = $"otpauth://totp/{accountTitle}?secret={setupInfo.ManualEntryKey}";
     var options = new QrCodeEncodingOptions()
     {
         DisableECI = true,
         CharacterSet = "UTF-8",
         Width = qrCodeWidth,
         Height = qrCodeHeight,
         Margin = 0,//指定生成条形码时使用的边距 二维码不需要?
     };
     var qr = new ZXing.ZKWeb.BarcodeWriter();
     qr.Options = options;
     qr.Format = BarcodeFormat.QR_CODE;

     Bitmap bitmap = new Bitmap(qr.Write(content));
     Graphics g = Graphics.FromImage(bitmap);
     g.DrawImage(bitmap, 142, 410);//142与410设置的意义,改为0、0有什么区别

     MemoryStream stream = new MemoryStream();
     bitmap.Save(stream, ImageFormat.Jpeg);
     byte[] data = new byte[stream.Length];
     stream.Position = 0;
     stream.Read(data, 0, data.Length);
     stream.Close();
     var str64 = Convert.ToBase64String(data);
     return str64;
     #endregion
 }

 

 

2.验证码校验

 /// <summary>
 /// 验证TFACode
 /// </summary>
 /// <param name="code"></param>
 /// <returns></returns>
 public async Task<bool> VerifyCode(string code, long userId)
 {
     var user = await _db.Queryable<User>().Where(u => u.Id == userId).FirstOrDefaultAsync();
     if (user == null)
         throw new CustomException(ErrorCode.LoginAccountIsNotExist);
     string keyCode = user.Account + "|" + user.TFASeed;
     string key = EncryptCodeHelper.MD5Encrypt(code);
     return TFAHelper.ValidateTwoFactorPIN(key, code);
 }

TFAHelper.ValidateTwoFactorPIN

/// <summary>
/// 验证TFA验证码
/// </summary>
/// <param name="accountSecretKey"></param>
/// <param name="code"></param>
/// <returns></returns>
public static bool ValidateTwoFactorPIN(string accountSecretKey, string code)
{
    TwoFactorAuthenticator twoFactor = new TwoFactorAuthenticator();
    return twoFactor.ValidateTwoFactorPIN(accountSecretKey, code, TimeSpan.FromMinutes(5));//目的是为了防止服务器时间和手机时间有差异,默认5分钟
}
 
工作流程:
  1. 密钥共享:服务器为每个用户生成唯一的32位密钥,通过二维码安全传递给认证器应用
  2. 时间同步:客户端和服务端基于UTC时间保持同步
  3. 动态生成:每30秒自动计算生成新的6位数字验证码
  4. 验证比对:用户输入验证码后,服务器用相同算法重新计算并比对

使用流程:

        1.用户使用手机APP认证器(如 Google Authenticator、Zoho OneAuth、AuthenticatorPro等)扫描服务器生成的二维码,扫描成功后APP会生成一个动态验证码的账户(自定会账户信息)

        2.根据APP中对应二维码生成账号的动态验证码,去服务器对应的登录等场景的验证码位置输入,服务器进行验证码的校验

posted @ 2025-12-15 16:30  流年sugar  阅读(7)  评论(0)    收藏  举报