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分钟 }
工作流程:
-
密钥共享:服务器为每个用户生成唯一的32位密钥,通过二维码安全传递给认证器应用
-
时间同步:客户端和服务端基于UTC时间保持同步
-
动态生成:每30秒自动计算生成新的6位数字验证码
-
验证比对:用户输入验证码后,服务器用相同算法重新计算并比对
使用流程:
1.用户使用手机APP认证器(如 Google Authenticator、Zoho OneAuth、AuthenticatorPro等)扫描服务器生成的二维码,扫描成功后APP会生成一个动态验证码的账户(自定会账户信息)
2.根据APP中对应二维码生成账号的动态验证码,去服务器对应的登录等场景的验证码位置输入,服务器进行验证码的校验

浙公网安备 33010602011771号