背景:由于HTTP 协议是无状态的,为了应对交互式web的需求,cookie、session、token诞生。
Cookie
将数据保存到用户端。看一个记录用户购物车商品的需求:
随着购物车内的商品越来越多,每次请求的 cookie 也越来越大,对后面的每个请求都会造成一个大的负担。
Session
用户数据保存在服务端,将sessionId保存到cookie中返回给前端,以后的每次请求都只需携带sessionId。
session问题:客户端请求后,通过负载均衡会将请求打到不同的服务端,进而找不到对应的session。
解决方法:
- session 复制:保证每台服务端都有全部的session会话信息。由于同一个session保存了多份造成了数据冗余,对于节点多的情况造成整体的性能消耗也会很大。
- session粘连:通过配置ip_hash让相同 IP 的请求在负载均衡时都打到同一台机器上。但无法应对对应的机器挂了的情况。
- session 共享:将 session 保存在 redis等中间件中,请求到来时再去这些中间件取一下 session 。每个请求都要去 redis 取一下 session,多了一次内部连接带来的性能消耗,另外为了避免单点故障、保证 redis 的高可用,必须做集群。
Token
Token:一个加密的字符串,通过MD5等一些不可逆加密算法实现,可以保证唯一性。
JWT(Json Web Token):属于一种特殊的token,由三部分组成,它们之间用圆点(.)连接。
- Header --> token的类型 + 签名算法
- Payload --> 声明
- Signature --> 签名部分,利用header中指定签名算法对编码过的header + 编码过的payload + 一个秘钥进行签名。例如:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
JWT和Token的区别:主要体现在验证时是否需要查询数据库。对于客户端发送过来的普通token来说,服务端需要查询数据库检查记录是否存在、token的过期时间以及用户id是否匹配等进行token的验证。对于JWT,服务端会取出 token 中的 header + payload,根据密钥生成签名,然后再与发送过来的token 中的签名比对,如果成功则说明签名是合法的,即 token 是合法的。
另外由于payload 中保存着用户信息,所以拿到 token 后进行解析就可获取用户信息,避免了像 session 那样要从 redis 去取的开销。jwt可以保存在用户端的cookie或者localStorage中,当保存在localStorage 中时可以避免 CSRF 跨站请求伪造攻击。
JWT应用场景:
- 单点登录(SSO):在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。由于Cookie 跨站不能共享,使用 Cookie 实现单点登录就很复杂,但使用JWT只要在 header 中加上 token 即可完成所有跨域站点的认证。
- 移动端应用:移动端应用通常是无状态的,而Session 基于服务器端的状态管理,如果使用 Session 进行身份认证,移动应用需要频繁地与服务器进行会话维护,增加了网络开销和复杂性;
JWT的缺点:
-
没办法主动失效:JWT 不能像 Session 一样被强制无效。
-
将 JWT 存入数据库:将有效的 JWT 存入数据库中比如 Redis。如果需要让某个 JWT 失效就直接从 Redis 中删除这个 JWT 即可。但是,这样会导致每次使用 JWT 都要先从 Redis 中查询 JWT 是否存在的步骤,而且违背了 JWT 的无状态原则。
-
黑名单机制:和上面的方式类似,使用内存数据库比如 Redis 维护一个黑名单,如果想让某个 JWT 失效的话就直接将这个 JWT 加入到 黑名单 即可。然后,每次使用 JWT 进行请求的话都会先判断这个 JWT 是否存在于黑名单中。
-
-
JWT 的续签问题
- 用户登录返回两个 JWT:第一个是 accessJWT:JWT 本身的过期时间比如半个小时,另外一个是 refreshJWT 它的过期时间更长一点比如为 1 天。客户端登录后,将 accessJWT 和 refreshJWT 保存在本地,每次访问将 accessJWT 传给服务端。服务端校验 accessJWT 的有效性,如果过期的话,就将 refreshJWT 传给服务端。如果有效,服务端就生成新的 accessJWT 给客户端。否则,客户端就重新登录即可。