前言
由于HTTP是一种没有状态的协议,它并不知道是谁访问了我们的应用。这里把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下次这个客户端再发送请求时候,还得再验证一下。
token的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。
当用户第一次登录后,服务器生成一个token并将此token返回给客户端,以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。
简单token的组成;uid(用户唯一的身份标识)、mac地址、time(当前时间的时间戳)、JWT、sign(签名,token的前几位以哈希算法压缩成的一定长度的十六进制字符串。为防止token泄露)。
token机制的身份认证
使用token机制的身份验证方法,在服务器端不需要存储用户的登录记录。大概的流程:
- 客户端使用用户名和密码请求登录。
- 服务端收到请求,验证用户名和密码。
- 验证成功后,服务端会生成一个token,然后把这个token发送给客户端。
- 客户端收到token后把它存储起来,可以放在cookie或者Local Storage(本地存储)里。
- 客户端每次向服务端发送请求的时候都需要带上服务端发给的token。
- 服务端收到请求,然后去验证客户端请求里面带着token,如果验证成功,就向客户端返回请求的数据。
token几种常见的实现方式
1.存储型
存储型token,就是根据token这个字符串,可以在服务端存储上查到用户信息,那就是校验成功,如果查不到,那就是校验失败。 对于QPS很低的应用,可以使用MySQL来做存储,对于QPS很高的,可以使用Redis来做存储。这个方案的优势是,简单清晰,易于实现,且很自由,逻辑控制完全在服务端,比如踢谁下线、统计多少人登录,都一目了然;缺点是,这个解析的强依赖于这个存储,存储响应慢,那解析延时就高,存储可靠性低,那解析可靠性就不会高。虽说MySQL Cluster或者Redis Cluster已经很可靠的,但网络抖动,就真的无能为力了,网络一抖,解析就失败或超时。 且,成本其实很高的,每个用户的每次登陆,都需要在存储中记录一个数据,如果是MySQL还好,如果是Redis,那内存成本其实不小,感兴趣的可以自己算下。
2.计算型
计算型token,就是把用户信息如userId加密成一个字符串,这个字符串就是token,那么每次解析其实就是解密,解密出明文,比如userId,generateTimestamp,那么再根据generateTimestamp判断是否过期,如果解析都失败了,那就直接失败。 这个方案的优点是,性能很好,延时极低,且做到了真正的服务端无状态,不依赖任何外部存储,单看服务的话,可靠性几乎是最高的;但是缺点也很明显,完全依赖加密算法,那如果被破解,就完蛋了,这个可以考虑定期更换密钥,但是登录态信息完全放在客户端,服务端对的登录态的控制就很难了,比如踢谁下线,统计登录用户几乎不可能。
3.计算+存储
考虑到计算和存储的优劣势,我们可以考虑结合他们。这里举个例子token是个加密后的密文,解密后可以分成好几个字段,分别代表userId,generateTime,randomString。那么,如果解析都失败了,那就直接失败了,如果解析成功了,再根据generateTime来判断是否过期,如果过期了,那也直接失败了,如果都通过了,在拿randomString去存储中查询,以Redis为例,我们的存储结构可以设计成key是userId,value是sorted set,其中每个元素就是randomString,查得到就是合法,查不到就是非法。 优点是,那么在极端情况下,如果Redis访问失败/超时,那也可以退回成纯计算型token,暂时不去校验randomString,等Redis恢复后继续校验;缺点是,实现复杂。 说到这里可靠性其实已经很高了,但延时呢?
4.长短token
这里借鉴了缓存的概念,当然这里的缓存不是Redis。以PC为例,cookie里可以set两个,longToken,shortToken,其中longToken可以使用第三步说的计算+存储来实现,那么shortToken呢?每次校验登录态时,同时传入longToken,shortToken,如果没有shortToken,那么就去解析longToken,解析完之后如果成功,就生成一个新的shortToken给客户端;如果有shortToken,那么就去解析shortToken,shortToken完全使用加密的方式,如果shortToken解析成功就算成功。 需要注意shortToken的有效期一定要合适,这里的shortToken其实就是缓存,如果有效期合适的话,大部分请求都会由shortToken解析出来,避免了对存储的网络调用。如果有效期太长,会不安全,且踢出某个用户可能会有延时才能生效,有效期如果太短的话,那缓存效果可能不明显,所以需要结合业务特性来做决定。
token的存储时长
token可以存到数据库中,但是有可能查询token的时间会过长导致token丢失(其实token丢失了再重新认证一个就好,但是别丢太频繁,别让用户没事儿就去认证)。默认7200秒也就是2小时;一般情况设置为1800秒30分钟;最长永久。
token刷新会重新记时。
为了避免查询时间过长,可以将token放到内存中。这样查询速度绝对就不是问题了,也不用太担心占据内存,就算token是一个32位的字符串,应用的用户量在百万级或者千万级,也是占不了多少内存的。
token的加密
token是很容易泄露的,如果不进行加密处理,很容易被恶意拷贝并用来登录。加密的方式一般有:
- 在存储的时候把token进行对称加密存储,用到的时候再解密。
- 文章最开始提到的签名sign:将请求URL、时间戳、token三者合并,通过算法进行加密处理。
最好是两种方式结合使用。