Node.js躬行记(32)——F2A实战

Node.js躬行记(32)——F2A实战
F2ATwo-Factor Authentication其实是2FA的笔误都表示双因素认证。它是一种安全验证机制要求用户在登录时提供两种不同类型的证据因素来证明身份。在实际应用中F2A/2FA 最常见的表现形式是密码 短信验证码最普遍但安全性相对较低易受 SIM 卡交换攻击。密码 基于时间的一次性密码 the Time-Based One-time Password TOTP如 Google Authenticator、Microsoft Authenticator 等应用生成的 6-8 位动态数字。密码 硬件密钥如 YubiKey安全性最高能有效防御网络钓鱼。我们的后台将会采用第二种表现形式后台服务端基于 Node.js 实现。核心实现原理是基于时间同步的一次性密码TOTPTime-based One-Time Password算法通过“共享密钥”和“时间”两个要素在用户设备与服务器之间生成相同的动态验证码。即服务器和你的手机APP各自使用同一个密钥和当前时间通过相同的算法计算得出一串相同的数字。一、生成密钥1speakeasy.jsNode的生态提供了speakeasy.js库可以生成F2A的密钥还有供F2A设备扫描的地址。npm install --save speakeasy在项目中安装完成后就是调用 speakeasy 的生成方法。router.get( /get/f2a, async (ctx) { const secret speakeasy.generateSecret({ length: 20 }); ctx.body { code: 0, data: { secret: secret.base32, url: secret.otpauth_url, } }; }, );secret 就是一串字符而 url 是个 otpauth 协议的地址生成二维码后便于设备扫描。{ secret: M4STOUBFGM4UUXJXKZJXS5J4IIWEA3KU, url: otpauth://totp/SecretKey?secretM4STOUBFGM4UUXJXKZJXS5J4IIWEA3KU }2账户绑定密钥点击上图中的重新绑定按钮就能将当前 secret 和后台账户绑定起来在账户表中新增两个字段otpauthUrl 和 secret。{ _id: { $oid: 5f81288d3578bb005a79cdc1 }, status: 1, realName: 测试, userName: xxxx.me, cellphone: 13800138000, password: abcd, otpauthUrl: otpauth://totp/SecretKey?secretPU4WG5RDJVZVCSDHMM5UYSBFEMSEANBQ, secret: PU4WG5RDJVZVCSDHMM5UYSBFEMSEANBQ }后续在登录输入安全码后就可进行校验了。二、绑定应用1APP在应用市场提供了很多的APP用于显示安全码界面基本上都是下图这样例如 authy、2FAS 等。2小程序有个叫二次验证码的小程序也能用于绑定。3飞书小助手还设计了一种更简便的查询方式。在机器人的对话框输入安全码空格后面跟上自己管理后台的账号邮箱例如“安全码 xxxx.com”。如此就能得到该账户的最新安全码。原理就是查表然后调用 speakeasy 的 totp() 方法再调用飞书的接口发送消息。const account await this.models.BackendUserAccount.findOne({ userName: email }); const token speakeasy.totp({ secret: account.secret, encoding: base32, }); // 发送回复消息 await this.messageService.sendTextMessage(chat_id, token);不过还有个小问题就是也能收到别人的安全码。但后台都有查询记录若出现问题还能溯源。三、F2A校验1开关增加一步登录校验在使用时会增加门槛所以在通用配置中设计了一个开关。{ isOpen: false, whiteList: [ yyyy.com ] }只有在 isOpen 为 true 时才会开启校验。whiteList 是给特殊账户开启的白名单不需要走校验例如给第三方用的账户。2登录登录设计成了两步两次调用的接口都是 user/login。第一步还是原先的输入邮箱和密码。输入完后去请求登录接口判断是否需要二次校验若返回JSON包含 f2a。{ f2a: true }则显示弹框安全码为必填项点击确定进行二次校验。下面是登录接口中的部分逻辑从通用配置中读取开关信息判断是否弹框最后校验。// 判断是否需要F2A安全校验 const configContent await services.tool.getConfigContent({ key: 1a26f4185f66d6f94ef3897f7a475305 }); if(configContent configContent.isOpen configContent.whiteList.indexOf(userName) -1) { // 未传随机码说明是第一步校验直接返回 if(!code) { ctx.body { f2a: true }; return; } // 校验随机码 const verifyInfo { secret: account.secret, encoding: base32, token: code, } const verify speakeasy.totp.verify(verifyInfo); if(!verify) { ctx.status 400; ctx.body { error: 安全码错误 }; return; } }3过渡期在开启这个功能前会有一段时间的过渡期在登录页面增加说明文档。