随身Token动态令牌工作原理的思考

前段时间在腾讯工作,新员工入职的时候都会配发一个Token配件,核心作用只有一个,那就是验证身份。腾讯内部的系统、网站访问大多都需要PIN + Token的形式,PIN为固定的个人密码,Token为动态令牌,这样动静结合可以大幅提高访问的安全性,防止内部重要资料泄露。

其实很多场景都会有类似的静态密码+动态Token的验证方式,比如暴雪游戏的手机App令牌、中国银行的EToken,原理类似,大概长这个样子:

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 类型和采用的加密算法:

{
    "alg": "HS256",
    "typ": "JWT"
}
Payload 负载

这部分就是我们存放信息的地方了,可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者)exp(过期时间)sub(面向的用户)aud(接收方)iat(签发时间)

{
    "iss": "alexzhli JWT",
    "iat": 1441593502,
    "exp": 1441594722,
    "aud": "www.all1024.com",
    "sub": "www.all1024.com"
}

HeaderPayload部分都是由Base64编码组成

Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。它会使用 Base64 编码组成 JWT 结构的第一、二部分。

Signature 签名

前面两部分都是使用 Base64 进行编码的,即可以解开知道里面的信息。Signature 需要使用编码后的 headerpayload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。

三个部分通过.连接在一起就是 JWT 了,它可能长这个样子,长度貌似和你的加密算法和私钥有关系。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s

其实到这一步可能就有人会想了,HTTP 请求总会带上token,这样这个token 传来传去占用不必要的带宽啊。如果你这么想了,那你可以去了解下 HTTP2HTTP2 对头部进行了压缩,相信也解决了这个问题。

签名的目的

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的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设备,也许就像是核武器发射?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlexGeek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值