前段时间在腾讯工作,新员工入职的时候都会配发一个Token配件,核心作用只有一个,那就是验证身份。腾讯内部的系统、网站访问大多都需要PIN + Token的形式,PIN为固定的个人密码,Token为动态令牌,这样动静结合可以大幅提高访问的安全性,防止内部重要资料泄露。
其实很多场景都会有类似的静态密码+动态Token的验证方式,比如暴雪游戏的手机App令牌、中国银行的EToken,原理类似,大概长这个样子:


笔者是一个对技术相对痴迷的人,世间万物皆可以程序化,喜欢思考其内在逻辑,本篇就简单记录一下之前工作中对于Token的简单思考~
1. 令牌会存储在数据库中吗?
最开始的念头就是,会不会有一个数据库表专门用于存储用户 + Token
。如果是这样的话,姑且不说数据量的庞大、分库分表、读写性能、定时更新同步任务、存储及传递安全性等,仅仅是Token
硬件频繁的数据交互,可能会导致一大笔额外的流量开支。数据库存储肯定是有点扯的。
其实,作为一个开发,一看到Token
,就应该想到session
。上面的思考主要是session
的交互方式,而Token
本身一定不会形成后端的数据存储的,既然这些随身硬件都叫Token
,那么其使用的核心机制一定是和我们常规认知的Token
方式是一致的。
2. 扯一下开发常见的JWT,权当回顾
既然是常规的Token
方案,那么应该可以通过常见的JWT
来去思考。
JWT
是一个开放标准(RFC 7519
),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON
对象的形式安全传递信息的方法。JWT
可以使用 HMAC
算法或者是 RSA
的公钥密钥对进行签名。它具备两个特点:
- 简洁(Compact) - 可以通过
URL
,POST
参数或者在HTTP header
发送,因为数据量小,传输速度快 - 自包含(Self-contained) - 负载中包含了所有用户所需要的信息,避免了多次查询数据库
JWT的组成部分
Header 头部
头部包含了两部分,token 类型和采用的加密算法:
1 | { |
Payload 负载
这部分就是我们存放信息的地方了,可以把用户 ID
等信息放在这里,JWT
规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者)
,exp(过期时间)
,sub(面向的用户)
,aud(接收方)
,iat(签发时间)
。
1 | { |
Header
和Payload
部分都是由Base64
编码组成
Base64
是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。它会使用Base64
编码组成JWT
结构的第一、二部分。
Signature 签名
前面两部分都是使用 Base64
进行编码的,即可以解开知道里面的信息。Signature
需要使用编码后的 header
和 payload
以及我们提供的一个密钥,然后使用 header
中指定的签名算法(HS256
)进行签名。签名的作用是保证 JWT
没有被篡改过。
三个部分通过.
连接在一起就是 JWT
了,它可能长这个样子,长度貌似和你的加密算法和私钥有关系。
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s |
其实到这一步可能就有人会想了,HTTP
请求总会带上 token
,这样这个 token
传来传去占用不必要的带宽啊。如果你这么想了,那你可以去了解下 HTTP2
,HTTP2
对头部进行了压缩,相信也解决了这个问题。
签名的目的
最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT
的话,那么服务器端会判断出新的头部和负载形成的签名和JWT
附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
信息暴露
这里一定有一个问题:Base64
是一种编码,是可逆的,那么我的信息不就被暴露了吗?
是的。所以,在JWT
中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID
。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT
中了。如果将用户的密码放在了JWT
中,那么怀有恶意的第三方通过Base64
解码就能很快地知道你的密码了。
因此JWT
适合用于传递一些非敏感信息。JWT
还经常用于设计用户认证和授权系统,甚至实现应用的单点登录。
3. 一个有趣的现象
之前工作的组内,每个人都会有Token
,有工作6、7年的老员工,也有像我这样刚入职的新人,因此每个人的Token
设备使用时长不一样。对于我们这样的新员工而言,新的Token
总是很准确,Token
更新与验证也十分精准;而很多老员工的Token则会出现这样一样现象,在Token更新前数秒内、或者刚更新数秒内进行验证,经常会失败。
4. 最后的结论
上面的现象说明,Token
的生成一定是与时间戳有关的,想到这里便慢慢豁然开朗。时间差恰恰说明了,Token
设备并不是进行实时数据传输而进行验证,在Token
设备内和大服务后台分别有一套Token
的生成机制,他们以时间戳作为纽带进行关联,因此产生心跳差异
是正常的。
之所以不同年限的设备会产生时间误差,就是因为设备使用太久后,其设备内部本身的时间戳会产生误差,随着时间的积累误差不断增大,但这个误差不会产生质变。举个例子,Token
每5分钟更新一次,头尾差5s
,也仅仅有 10 / 300
的误差概率,只要避免在快更新或刚更新Token
时进行验证,基本不会有什么影响,如果误差过大,那么Token
设备中的计时机制是不合格的,需要返工。
那么时间是核心,基于时间进行哈希是不够的,需要对身份进行验证,因此需要对user id + 时间
进行非对称加密,然后再通过哈希算法,将一个长串的Token
哈希为N位数字
,考虑到哈希碰撞的可能性,一般令牌通常设置为6位左右。
那么定时更新是怎么实现的呢?其实也很简单,例如5分钟更新一次,那么00:00 和 00:03的时间戳在计算时会归为一致,向前或向后对齐。然后每几秒刷新一次令牌显示即可。
上一节对于JWT
的介绍,有一点脱了裤子放屁的感觉,但核心的非对称加密原理是一致的,写本篇随笔的时候联想到了,就把一些理论知识补充了进来~
由于移动应用的普及,常规的Token
硬件使用也越来越少,因为完全可以使用一个App
或小程序
进行代替,区别就是硬件设备中的计算机制拿到了App
前端,服务端的还是不变。但在一些极其需要安全考虑的场景,可能还是会做成小小的Token
设备,也许就像是核武器发射?
如有疑问,欢迎添加我的个人微信:
