nodejs 接入 google 身份验证器(authenticator)
所需接口 ​
- 获取令牌绑定信息:没有绑定就返回二维码 base64 编码(后端会在这一步生成临时密钥)
- 绑定令牌:接受前端返回的一次性码,并验证是否正确(后端会在这一步把临时密钥持久化)
- 解绑令牌:把持久化的密钥删除即可。
功能实现 ​
OK,现在流程弄清楚了,接下来就是正式的编码环节了。
我们需要用到 otplib 和 qrcode 两个包。其中 otplib 是一个 js 版本的 TOTP 实现(类似的还有 speakeasy ,一些早期教程里会用到,但是这个包已经不维护了),而 qrcode 就负责把种子密钥处理成二维码。
这里也有一个误区:otplib 不是对谷歌服务器接口的封装。他只是 TOTP 规范(RFC-6238)的一个实现。而谷歌身份验证器也遵守了相同的规范,所以对于相同的种子密钥,otplib 和谷歌身份验证器必定能生成相同的验证码,从而完成验证。
OK,接下来安装依赖:
bash 代码解读复制代码npm install otplib qrcode
npm install --save-dev @types/qrcode
下面是具体的功能代码,我就只贴核心逻辑了,其他的相关业务代码还是要看你的项目:
首先是第一个接口中的 获取令牌绑定信息:
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 代码解读复制代码import { authenticator } from 'otplib'
/**
* 判断令牌是否正确
*
* @param code 用户输入的一次性令牌
* @param secret 用户对应的种子密钥
* @returns {boolean} 一次性令牌是否正确
*/
const isCodeCorrect = (code, secret) => {
return authenticator.check(code, secret)
}
更简单了,调用 authenticator.check
,传入前端返回的用户一次性令牌和种子密钥,他就会返回验证码是否正确。绑定令牌和后续的验证都是这个 api,区别就是绑定时 secret 是从临时存储里获取的,而验证时是从用户的持久化存储里获取的。
由于 TOTP 是离线计算的,所以这整个过程都不需要接入什么第三方接口。
至于解绑令牌,也是输入一个一次性码,验证正确后就把持久化的种子密钥删除即可。