多因素身份验证(MFA)指南

标题:

一、引言

  • 什么是 MFA?   多因素身份验证(Multi-Factor Authentication, MFA)是一种安全机制,要求用户提供两种或多种形式的身份证据来证明他们是合法用户。常见的形式包括:密码(知识因子)、物理令牌或智能手机应用生成的一次性密码(拥有因子)、生物特征识别(生物特征因子)等。

二、MFA 校验流程

  1. 初始化设置

    • 登录目标账户(例如阿里云账号或其他在线服务)。

    • 寻找并进入账户安全设置或相关页面以启用 MFA。

    • 选择使用虚拟 MFA(如基于 RFC 6238 的 TOTP 应用)。

    • 下载并安装支持 MFA 的应用程序,如 Google Authenticator、FreeOTP 或 Microsoft Authenticator。

    • 扫描显示的二维码或手动输入共享密钥到应用程序中创建新的 MFA 配置。

  2. 日常使用

    • 输入用户名和密码后,系统会提示输入由 MFA 应用生成的动态验证码。

    • 在 MFA 应用中获取当前的有效验证码,并将其准确无误地输入到网站或应用的验证框内。

    •  git addgit commit -m “Merge upstream changes and resolve conflicts”plaintext

三、常见问题与解决方案

:::
常见问题与解决方案

:::
[火箭] MFA 验证码校验不通过

问题描述:如果输入的验证码无法通过验证,请确认以下几点:

验证码是否已过期(通常每 30 秒或 60 秒更新一次)。

是否从正确的 MFA 设备获取了验证码。

是否针对同一账户进行验证。

如有变更,确保已在新设备上重新绑定 MFA。
:::
:::
[火] 重置或解绑 MFA

当丢失设备或需要更换 MFA 方式时,需通过账户管理后台的安全措施来进行 MFA 重置或解除绑定。

通常这一步骤可能需要进行身份验证的备份方法,如接收短信验证码或电子邮件确认。
:::
:::

四、最佳实践

  • 启用 MFA 的重要性

    • 强烈建议所有用户启用 MFA,因为它极大地提高了账户安全性,防止恶意攻击者仅凭单一密码窃取账户控制权。
  • 备用验证方式

    • 始终保持至少一种备用验证方式的更新,以便在主 MFA 设备不可用时能访问账户。

[魔法棒挥动] 实用教程

建议先看一下阮一峰老师的 2FA 教程。

双因素认证(2FA)教程

1、各大 MFA 程序的识别的信息的规则(不是 HTTP):

otpauth://totp/Muieay:admin?issuer=Muieay&secret=ELXVKSD53UJAU7EGZ2YIIISZMI

约定字符串格式如下:

++otpauth://totp/[ 客户端显示的账户信息 ]?secret=[secretBase32]++

2、简单代码实现原理,如下

1
2
3
4
5
6
7
QrConfig config = new QrConfig();
String secretBase32 = "cm56gdhcjo1cs8pihg0qbmeudp280isq";
// otpauth://totp/1Panel:admin?issuer=1Panel&secret=ELXVKSD53UJAU7EGZ2YIIISZMI
String qrCode = "otpauth://totp/Hello1:Hello2?issuer=Hello3&secret=" + secretBase32;
// 工具类生成二维码
config.setErrorCorrection(ErrorCorrectionLevel.H);
QrCodeUtil.generate(qrCode, config, FileUtil.file("E:\c\qrcodeCustom.jpg"));

Spring引入MFA

  1. 引入依赖 ++java-totp++
1
2
3
4
5
<dependency>
<groupId>dev.samstevens.totp</groupId>
<artifactId>totp</artifactId>
<version>1.7.1</version>
</dependency>
  1. 生成二维码测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SecretGenerator secretGenerator = new DefaultSecretGenerator();
String secret = secretGenerator.generate();
System.err.println("secret = " + secret);
// secret = "BP26TDZUZ5SVPZJRIHCAUVREO5EWMHHV"
QrData data = new QrData.Builder()
.label("example@example.com")
.secret(secret)
.issuer("AppName")
.algorithm(HashingAlgorithm.SHA1) // More on this below
.digits(6)
.period(30)
.build();
QrGenerator generator = new ZxingPngQrGenerator();
byte[] imageData = generator.generate(data);
// 将字节数据返回图片保存在本地
FileUtil.writeBytes(imageData, "E:\static\qrcodeCustom2.jpg");
// otpauth://totp/example%40example.com?secret=QUF7EQA5M7CVJWE6UD5SCPTWKJLAXUMO&issuer=AppName&algorithm=SHA1&digits=6&period=30
  1. 验证 MFA 二维码测试
1
2
3
4
5
6
7
8
9
10
11
12
TimeProvider timeProvider = new SystemTimeProvider();
CodeGenerator codeGenerator = new DefaultCodeGenerator();
CodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);

// secret = the shared secret for the user
// code = the code submitted by the user
// 模拟传入code和secret数据
String code = "470217";
String testSecret="QUF7EQA5M7CVJWE6UD5SCPTWKJLAXUMO";

boolean successful = verifier.isValidCode(testSecret, code);
System.err.println("successful = " + successful);

以下是简单的接口实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@RestController
@RequestMapping("/qr")
public class QRController {
@Resource
private QrTestService qrTestService;

/**
* 调用接口直接生成二维码返回前端
* @return
* @throws QrGenerationException
*/
@GetMapping("/getQr")
public ResponseEntity<byte[]> getQrCode() throws QrGenerationException {
SecretGenerator secretGenerator = new DefaultSecretGenerator();
// 生成密钥
String secret = secretGenerator.generate();
// secret = "BP26TDZUZ5SVPZJRIHCAUVREO5EWMHHV"
qrTestService.save(new QrTest(secret));
QrData data = new QrData.Builder()
.label("hello@qq.com")
.secret(secret)
.issuer("AppName")
.algorithm(HashingAlgorithm.SHA1) // More on this below
.digits(6)
.period(30)
.build();
QrGenerator generator = new ZxingPngQrGenerator();
byte[] imageData = generator.generate(data);
// 设置返回类型为图像(PNG格式)
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_PNG)
.body(imageData);
}


@GetMapping("/check/{id}/{code}")
public String checkCode(@PathVariable Integer id,@PathVariable String code) {
TimeProvider timeProvider = new SystemTimeProvider();
CodeGenerator codeGenerator = new DefaultCodeGenerator();
CodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
// secret = the shared secret for the user
// code = the code submitted by the user
// 模拟前端输入
QrTest qrTest = qrTestService.getById(id);
String testSecret = qrTest.getSecret();
boolean successful = verifier.isValidCode(testSecret, code);
return successful ? "success" : "fail";
}
}

相关参考链接

https://ruanyifeng.com/blog/2017/11/2fa-tutorial.html

https://www.cnblogs.com/loveyou/p/6989064.html

https://github.com/samdjstevens/java-totp

https://github.com/hectorm/otpauth

https://github.com/suyin58/otp-demo/tree/master