一、JWT出现的原因
1.1 http无状态协议
http是无状态的协议,也就是说当客户端发来请求时,服务端并不清楚该请求是哪台主机发出的,因此需要有一种识别和鉴定机制来实现这一需求
1.2 传统上是采用给用户分配唯一session_id的方式实现这一需求
1)客户端登录成功后,服务器会给每一台主机分配一个唯一的session_id,用来区分他们,除了存入服务器的缓存,数据库或者内存中,还会把这个session_id返回给相应的主机,
2)主机收到session_id后会存入cookie中,以后主机的每一次发送其他类型的请求的操作都会携带这个session_id
3)服务器会将客户端发来的这个session_id和服务端查到的session_id进行对比,如果匹配,则返回给对应主机所需要的资源,否则拒绝
1.3 缺点
1.因为服务器缓存session_id,需要定期清理session_id表,否则及其占用内存和资源
2.不能识别跨域请求
3.在分布式(即多台服务器)的环境下,还得做session同步
二、JWT
2 原理
全称是json web token, 简单来说JWT就是通过可逆加密算法,生成一串包含用户、过期时间等关键信息的Token,每次请求服务器拿到这个Token解密出来就能得到用户信息,从而判断用户状态。
1.客户端登录成功后(必须是在登陆成功才行,与session一样的前提条件),服务器会根据用户名和签名以及其他信息加密生成唯一的token串,用来区分他们,不需要存入服务端的缓存中,但会把这个token返回给相应的主机
2.主机收到token后会存入cookie或者localStorage中,以后主机的每一次发送其他类型的请求的操作都会携带这个token
3.服务器会将客户端发来的这个token和服务端从数据库查询出来的并且重新计算得到的用户信息进行对比,如果匹配,则认证成功,如果用户请求的资源需要相应的权限,则校验token中的payload中存储的权限等相关信息,如果有权限则返回给对应主机所需要的资源(即做到了权限鉴权),否则拒绝
3 token+redis与jwt的区别
1.简单的说,token只是一个标识,以token加redis为例,服务端将token保存在redis中,客服端访问时带上token,如果在redis中能够查到这个token,说明身份有效。
2.jwt不需要查库,本身已经包含了用户的相关信息,可以直接通过服务端解析出相关的信息,与session,token的最大区别就是服务端不保存任何信息(服务端只需要保存密钥key)
4 登出与多台设备登录
4.1 用户登出,如何设置token无效?
1.方案一:
JWT是无状态的,用户登出设置token无效就已经违背了JWT的设计原则,但是在实际应用场景中,这种功能是需要的,那该如何实现呢?提供几种思路:
用户登出,浏览器端丢弃token
使用redis数据库,用户登出,从redis中删除对应的token,请求访问时,需要从redis库中取出对应的token,若没有,则表明已经登出;
为了保持数据的一致性,每一次认证都需要从redis中取出对应的token,每一次都以redis中的token为准。
2.方案二(服务端的操作刚好与方案一相反):
用户登出,浏览器端丢弃token
登出时将相关的信息比如用户名存储在redis中,并设置过期时间。当再次访问时,从jwt中解析出用户名去redis中查找,如果存在则表示此jwt已登出失效。这里需要注意的是,如果用此方法,则验证jwt是否登出应该放在第一位。思考一个场景,如果redis中存储的是用户名,那么当用户登出后,redis中已经有了相应的用户名,当用户再次登录时,解析jwt发现此用户已登出,则jwt失效,所以在登录时要清空相关的登出缓存。
4.2 特殊场景:
同一个用户从两个设备登陆到服务端(比如设备1是电脑端的微信,设备2是手机端的微信),并且选择将token保存到redis当中,
手机端登出,删除redis中的对应的token - 设备2再次请求数据时,redis中的数据为空,需要重新登录。
很明显,这种情况是不应该出现的,说一下自己会怎么解决:
每一个设备id与用户id联合生成唯一的key,保存在redis中,即设备1的用户登出,只删除对应的token,设备2的token仍然存在
服务器端维护一个版本号,相同用户不同设备登入,版本号加1,这样保持key的唯一性(和上面差不多),当有设备登出时,版本号减1,并且检查版本号是否大于0,如果不是则清空redis中保存的token,
后端不存储token,只在登出的设备浏览器上清空token,那么另一台设备上的浏览器的cookie当中仍然存储着token,
4.3 如何防止同一个账号在两台类似的设备上登录
如何防止同一个账号在两台类似的设备上登录,比如不能同时在两台电脑上登录同一个微信号,但允许在手机和电脑上的微信登录都同时使用同一个账号登录?
方法一:
在redis缓存中放一个键值对,key存的的用户名,value存储的是1,1表示用户已经登录成功,如果这个键值对不存在则表明当前没有设备登录。当用户第一次登录时,value置为1,当当第二次登录时先检查redis缓存中是否存在用户名作为的key,如果存在表明已经在另一台设备上登录成功,禁止第二台设备的登录,当唯一的一台设备登出时,则删除这个键值对;
方法二:
不能对同一个账号重复登录(做法:服务器保存生成的token后再将其发送给用户,每次用户请求数据时,还是要把账号密码传给后端,如果密码不对直接拒绝登录,如果正确,服务器经由这个账号密码算出token,先验证新token和redis缓存中的token是否相同,然后再验证token是否有效。相同,则说明用户唯一,有效则说明用户合法。注意:就算传入token和数据库中/redis缓存的token相同,也不能说明用户合法,因为数据库中的token可能已经过期无效了,如果token过期就让用户重新登录,然后再次签发和保存token。ps:token必须具有时效性,否则用户数据被破解只是时间问题)
5 如何实现jwt续期
在jwt中保存过期时间,解析时进行判定,如果即将超时则重新设置过期时间返回一个新的jwt给客户端。
6 Token实现跨域以及单点登录:
1.什么是单点登录:单点登录就是在多个子系统中,用户只需一次登录,各个子系统即可感知该用户已经登录。
7 生成的token后在服务端有三种存储方案:
1.保存在redis,最常用,也是分布式下的验证token的解决方案,
2.数据库存储,性能比redis稍差,速度稍慢
3.不做保存,下次验证的时候直接用jwt.decode验证(服务端为express),存储的压力给到了客户端,但是每次从客户端传到服务器端的数据量会稍微大一些