一.技术选型
1.为什么不用Session+Cookie,而要用Token?
【1】无状态
什么是无状态服务
无状态服务是指在处理请求时不存储任何会话信息或状态信息的服务。这意味着每个请求都是独立且相互独立的,服务不会在请求之间保留任何状态。
Session-Cookie方案的缺陷
先回顾一下使用Session-Cookie方案:
1.用户成功登陆系统,会创建一个会话对象Session,然后返回给客户端器对应的SessionID的Cookie 。
2.当用户向后端发起请求的时候会把 SessionID 带上。拿到这个SessionID后后端就可以找到对应的Session从而获取你的身份状态。
单体项目用Session-Cookie方案是不错的,但是对于微服务项目,Session-Cookie方案就面临挑战。
举个例子:用户在A服务器进行了登录,此时用户的 Session 信息保存在 A 服务器。但是用户下次请求B服务器,需要用到该Session信息,此时由于 B 服务器没有保存 用户的 Session 信息,导致用户需要重新进行登陆。
存在一些解决方案。例如有几个方案可供大家参考:
1.某个用户的所有请求都通过特性的哈希策略分配给同一个服务器处理。这样的话,每个服务器都保存了一部分用户的 Session 信息。服务器宕机,其保存的所有 Session 信息就完全丢失了。
2.每一个服务器保存的 Session 信息都是互相同步的,也就是说每一个服务器都保存了全量的 Session 信息。当节点多的时候,同步成本高,并且系统的一致性和可靠性也会收到影响。
3.单独使用一个所有服务器都能访问到的数据节点(比如缓存)来存放 Session 信息。为了保证高可用,数据节点尽量要避免是单点。
4.Spring Session 是一个用于在多个服务器之间管理会话的项目。它可以与多种后端存储(如 Redis、MongoDB 等)集成,从而实现分布式会话管理。通过 Spring Session,可以将会话数据存储在共享的外部存储中,以实现跨服务器的会话同步和共享。
使用Token代替会话技术实现无状态认证
在JWT的中自包含了身份验证所需要的信息,因此,我们的服务器不需要存储 Session 信息。只需要在登录的时候返回给客户端一个Token密钥,然后每次请求各个微服务都携带这个Token解析获得用户信息即可。这种方案解决了原来Session-Cookie方案的问题,并减轻了服务端的存储压力,从而实现无状态服务。
【2】防止CSRF
CSRF(Cross Site Request Forgery) 一般被翻译为 跨站请求伪造 。
那么什么是跨站请求伪造呢?说简单点,就是用你的身份去发送一些对你不友好的请求,完成需要你权限的操作。
举个例子,比如银行转账的链接如果如下:
http://www.mybank.com/Transfer?bankId=11&money=10000
这个转账肯定需要你的权限,如果用Cookie,因为Cookie是无感知的发送,你点击链接的同时自动携带了带有sessionId的Cookie,那这个操作无意间完成了,然后你钱就没了。
而JWT一般会存放在localStorage 中。与Cookie不同的是,它不是点击链接就自动发送的,前端的每一个请求后续都需要通过JS代码附带上这个 JWT。所以即使你点击了链接,这个请求也不会完成,因为没有权限。
总结来说,Cookie无感知发送,需要你的权限的相关操作点击个链接就可能被完成。而JWT需要通过代码,单凭一个链接无法实现CSRF。
【3】适合移动端
移动端不能用想网页端一样的Cookie。然后如果硬要用session的话可能不同平台有不同的传递sessionId的方式。
而JWT是通用的。
【4】适合单点登录
首先服务端不同设备之间的session无法互通。
其次cookie不能跨域,比如sso.example.com接收的cookie只能在本域名下发送,如果用户尝试在另一个域名(如app.example.com)下发送请求,浏览器会根据同源策略阻止该 Cookie 被发送到另一个域名。除非在服务端允许跨域。
2.为什么不直接用账号和密码加密传回,而是用Token
【1】防止密码盗窃
因为如果用密码和账号加密传回的话如果被窃取用来伪造请求,这时候我们就必须让密码失效。而如果是Token,我们只需要在缓存中将token失效即可。(一般有缓存来控制token状态)
【2】无状态
在JWT的中自包含了身份验证所需要的信息,因此我们不用再去数据库中查。
二.相关功能实现
我们一般会用Redis维护Token,一般采用黑名单和白名单。
黑名单的话是指维护登出注销但未过期的Token,白名单是维护有效的Token。
1.登出功能的实现
【1】黑名单
登出的时候将过期的Token存入黑名单。每次请求判断是否在黑名单中。
【2】白名单
创建Token存入白名单中,注销的时候把Token删除。每次请求判断是否在白名单中。
2.实现单账号登录(顶号)和控制多账号登录
需要使用白名单。
单账号登录:键为userId,值只能一个。这样在另一个设备重新登录,则原先Token就被代替,实现顶号。
多账号登录:键为user,值可以list。可以控制列表的长度控制登录设备的多少。
3.续签(无感知登录)--双Token
第一个是 accessJWT ,它的过期时间 JWT 本身的过期时间比如半个小时,另外一个是 refreshJWT 它的过期时间更长一点比如为 1 天。refreshJWT 只用来获取 accessJWT,不容易被泄露。
客户端登录后,将 accessJWT 和 refreshJWT 保存在本地,每次访问将 accessJWT 传给服务端。服务端校验 accessJWT 的有效性,如果过期的话,就将 refreshJWT 传给服务端。如果有效,服务端就生成新的 accessJWT 给客户端。否则,客户端就重新登录即可。
这种方案的不足是:
- 需要客户端来配合;
- 用户注销的时候需要同时保证两个 JWT 都无效;
- 重新请求获取 JWT 的过程中会有短暂 JWT 不可用的情况(可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT);
- 存在安全问题,只要拿到了未过期的 refreshJWT 就一直可以获取到 accessJWT。不过,由于 refreshJWT 只用来获取 accessJWT,不容易被泄露。