nodejs 接入 google 身份验证器(authenticator)

所需接口

  • 获取令牌绑定信息:没有绑定就返回二维码 base64 编码(后端会在这一步生成临时密钥)
  • 绑定令牌:接受前端返回的一次性码,并验证是否正确(后端会在这一步把临时密钥持久化)
  • 解绑令牌:把持久化的密钥删除即可。

功能实现

OK,现在流程弄清楚了,接下来就是正式的编码环节了。

我们需要用到 otplibqrcode 两个包。其中 otplib 是一个 js 版本的 TOTP 实现(类似的还有 speakeasy ,一些早期教程里会用到,但是这个包已经不维护了),而 qrcode 就负责把种子密钥处理成二维码。

这里也有一个误区:otplib 不是对谷歌服务器接口的封装。他只是 TOTP 规范(RFC-6238)的一个实现。而谷歌身份验证器也遵守了相同的规范,所以对于相同的种子密钥,otplib 和谷歌身份验证器必定能生成相同的验证码,从而完成验证。

OK,接下来安装依赖:

bash
bash 代码解读复制代码npm install otplib qrcode
npm install --save-dev @types/qrcode

下面是具体的功能代码,我就只贴核心逻辑了,其他的相关业务代码还是要看你的项目:

首先是第一个接口中的 获取令牌绑定信息

js
js 代码解读复制代码import { authenticator } from 'otplib'
import QRCode from 'qrcode'

/**
 * 初始化 OTP 令牌
 *
 * @param userName 唯一的用户名
 * @param appName 项目名称
 * @returns secret 需要临时缓存的种子密钥
 * @returns qrcodeUrl 展示给用户的二维码 base64
 */
const createSeedSecret = async (userName, appName) => {
    const secret = authenticator.generateSecret()

    const googleKeyuri = authenticator.keyuri(userName, appName, secret)
    const qrcodeUrl = await QRCode.toDataURL(googleKeyuri)

    return { secret, qrcodeUrl }
}

核心代码就三行,authenticator.generateSecret 生成一个随机的种子密钥字符串,需要和用户名绑定暂存到诸如 redis 之类的地方,记得添加过期时间。

而 keyuri 则是 google 身份验证器的一个约定,用于生成一个 url,用户扫描后就能直接打开手机上的 google 身份验证器 app,注意,这里用到了用户名和应用名,扫描后会在 app 上以 “应用名(用户名)” 的形式显示出来。

keyuri 具体规范可以 看这里。而 otplib 中提供了这个规范的实现封装,也就是上面用到的 authenticator.keyuri 方法(同样是离线操作),其他的关于 google 身份验证器和 TOTP 的一些小区别可以 看这里

第三行就比较简单了,QRCode.toDataURL 把一个字符串处理成二维码的 base64,直接传递给前端 <img src={qrcodeUrl} /> 就能用。

然后是 验证一次性令牌是否正确

js
js 代码解读复制代码import { authenticator } from 'otplib'

/**
 * 判断令牌是否正确
 *
 * @param code 用户输入的一次性令牌
 * @param secret 用户对应的种子密钥
 * @returns {boolean} 一次性令牌是否正确
 */
const isCodeCorrect = (code, secret) => {
    return authenticator.check(code, secret)
}

更简单了,调用 authenticator.check,传入前端返回的用户一次性令牌和种子密钥,他就会返回验证码是否正确。绑定令牌和后续的验证都是这个 api,区别就是绑定时 secret 是从临时存储里获取的,而验证时是从用户的持久化存储里获取的。

由于 TOTP 是离线计算的,所以这整个过程都不需要接入什么第三方接口。

至于解绑令牌,也是输入一个一次性码,验证正确后就把持久化的种子密钥删除即可。

备案号: 湘ICP备2021015274号-3