背景
Google令牌,其实不局限于google令牌,其他的很多手机令牌背后的逻辑都是差不多的,毕竟密钥和令牌计算逻辑已经开源了。就不赘述了
实现逻辑
当前令牌 = fn (密钥, 当前时间), 也就是当前令牌的六位数字只与密钥和当前时间有关系。 密钥和令牌生成的算法已经开源了。也就意味着:
- 只要拿到密钥,就可以计算出当前的令牌。听着有点不安全,但是吧,密钥都泄露了,那还谈算法的安全不安全就没意义了。
- 因为只需要密钥和时间,所以内网环境下离线使用也是完全可以的,只是要确保内外网时间一致。
- 算法都是开源的,也就意味着你不用google-auth-library来做,用其他的封装好的npm包生成的密钥也是可以用手机上的谷歌令牌软件识别。(不严谨,意思是这么个意思)
实现
这里我用otplib来演示一下,如果想用google-auth-library也可,但是吧,就是后者在配置上相对麻烦一丢丢。
生成密钥
不多赘述,直接上代码。
import { authenticator } from 'otplib';
const secret = authenticator.generateSecret();
完了?额,确实完了,就一行代码。
核心就是调用的generateSecret方法来生成一个密钥。这个密钥有两个作用:
- 用来让手机上的谷歌令牌软件识别进行绑定。
- 当输入令牌后,令牌的校验。
令牌的绑定
通常会采用生成一个二维码的方式来做谷歌令牌的绑定。
// 生成google手机令牌可以识别的二维码URI
const googleKeyUri = authenticator.keyuri('当前用户名字','产品名字',secret);
// 然后直接渲染就好,这里使用的qrcode.react,生成一个二维码,vue或者在nodejs参考其他的二维码生成方式。
<QRCodeCanvas value={googleKeyUri} />
用户使用手机扫描二维码,令牌就成功绑定上了。
令牌的校验
不多赘述,直接上代码。
const { authenticator } = require('otplib');
const check = (六位令牌码) => authenticator.check(六位令牌码, secret);
调用authenticator的check方法即可在服务端进行校验,将手机令牌的六位令牌码和先前的密钥作为参数即可。
值得注意的地方
这里只是写了核心的代码,密钥和用户的服务端存储,二维码的展示方式,是否绑定成功的验证都没有写,具体可以根据实际需求扩展。
- 在服务端生成密钥二维码再给前端展示也是可以的。
- 要做的完善一点的话,我们在第一次绑定上的时候需要进行是否绑定成功的确认,难免会有一些未知原因,服务器没存上。
写在最后的话
动态令牌在当下的互联网发展中是非常常见的,其内部实现逻辑TOTP (Time-based One-Time Password)也是现在国内主流的,具体的密码散列函数有兴趣的小伙伴可以去查一查。