登录接口设计的优缺点——开发经验(3)

[!tip] 本文没有具体实现过程,只讨论各个登录方式的优缺点。

1、Cookie 和 Session

1.1、Cookie + Session 实现流程:

用户首次登录时:

  1. 用户访问登录接口
  2. 服务器验证密码无误后,会创建 SessionId,并将它保存起来。
  3. 服务器端响应这个 HTTP 请求,并通过 Set-Cookie 头信息,将 SessionId 写入 Cookie 中。
  4. 服务器端的 SessionId 可能存放在很多地方,例如:内存、文件、数据库等。
  5. 第一次登录完成之后,后续的访问就可以直接使用 Cookie 进行身份验证了:
    用户访问非登录接口的页面时,会自动带上第一次登录时写入的 Cookie。
    服务器端比对 Cookie 中的 SessionId 和保存在服务器端的 SessionId 是否一致。
    如果一致,则身份验证成功。

1.2、Cookie + Session 存在的问题

虽然我们使用 Cookie + Session 的方式完成了登录验证,但仍然存在一些问题:

  1. 由于服务器端需要对接大量的客户端,也就需要存放大量的 SessionId,这样会导致服务器压力过大。
  2. 如果服务器端是一个集群,为了同步登录态,需要将 SessionId 同步到每一台机器上,无形中增加了服务器端维护成本。
  3. 由于 SessionId 存放在 Cookie 中,所以无法避免 CSRF 攻击。

2、Redis+UUID

上述 cookie+session 最大的问题还是无法满足于分布式系统登录的需求
因为 session 存储在服务端是存储在单点的机器上面,如果需要实现多台集群之间的 session 同步又要涉及很多的一致性的问题得不偿失。
此时的解决方案就是将用户登陆的标识存储在一台速度快,性能好的单独的存储区域,所以 Redis+UUID 这种方案就被广泛使用。

  • [4] 原理:使用 UUID 生成全局唯一字符串作为 key,将用户的信息作为 value 存储在 Redis 中
    流程如下:
  • 访问登录接口时:
    校验账号密码,对比通过后生成 UUID 作为 key,将用户信息作为 value 存储在 Redis 中,随后将 UUID 作为token返回给客户端(浏览器)
  • 校验登录

[!note]+

  1. 浏览器拿到 UUID 之后,后续的请求在请求头上带上这个 token。
  2. 请求到后端时触发拦截器校验,校验请求头中是否带有 token,如果没有拦截。
  3. 拿到 token 之后去 Redis 中取出用户信息,如果 Redis 中没有该 Key 或者已经过期,就进行拦截。
  4. 取出用户信息之后存入 ThreadLocal 中,作为当前登录用户信息,方便后续使用。

如果是微服务系统,由于网关和业务分离,此时第 2,4 步需要跟改为如下:

[!note]+
2.请求到达网关时触发拦截器拦截校验,校验请求头中是否带有 token,如果没有拦截。
4.校验存在之后,将 Redis 的 key 存放到 head 中(因为网关和业务不在一个微服务,不是一个请求,不是一个线程,不能使用 ThreadLocal 共享信息),
5. 请求到业务微服务触发业务微服务的拦截器,再取出 Redis 中取出信息存储到 ThreadLocal 中

缺点:

[!warning] 不能防止盗刷
Redis+UUID 的最大的缺点之一就是不能防止盗刷。如果我们没有再单独设置限流的情况下,仅使用这种方式实现登录,那么假如说有一个恶意请求多次重复的登录该接口,就会导致 Redis 中存储大量的重复的该用户的信息,导致内存爆满。

[!tip]+ 解决方案
将 Redis 中的存储改为 Hash 类型,将 username 作为 key 将 UUID 作为 value 中的 hashkey 即可解决。

3、JWT

JWT 是现在比较火的一种登录校验方式,

[!note] 登录流程

  1. 客户端发送登录请求,校验成功之后将用户信息基于私钥和 JWT 生成的 token 字符串返回给前端。
  2. 后续的请求携带上这个 token 字符串,到达后端拦截器之后会将这个 token 解析出用户信息,如果解析失败或者没有携带,则拦截请求,转接登录界面。
  3. 如果登录成功则将用户信息存入到 Threadlocal 中。

[!success] 优点:

  1. 单点登录友好:相较于传统的 Cookie 和 Session 方案,JWT 方式的用户信息被加密在了 token 字符串中,存储在客户端,我们在设计分布式系统的时候不用在意用户登录信息同步问题。
  2. 由于后端不用存储用户信息,所以盗刷不会给系统造成很大的负担。

[!warning] 缺点:
无状态:后端无法实现 logout 功能,由于登录状态在于前端是否携带 token 字符串,所以后端无法手动登出。在以下几种需要手动登出的情况下,用户依然可以登录。

  1. 退出登录;
  2. 修改密码;
  3. 服务端修改了某个用户具有的权限或者角色;
  4. 用户的帐户被删除/暂停。
  5. 用户由管理员注销;

token 的续签问题:

token 有效期一般都建议设置的不太长,那么 token 过期后如何认证,如何实现动态刷新 token,避免用户经常需要重新登录?
用户登录返回两个 token :第一个是 acessToken ,它的过期时间 token 本身的过期时间比如半个小时,另外一个是 refreshToken 它的过期时间更长一点比如为 1 天。客户端登录后,将 accessToken 和 refreshToken 保存在本地,每次访问将 accessToken 传给服务端。服务端校验 accessToken 的有效性,如果过期的话,就将 refreshToken 传给服务端。如果有效,服务端就生成新的accessToken 给客户端。否则,客户端就重新登录即可。该方案的不足是:

  1. 需要客户端来配合;
  2. 用户注销的时候需要同时保证两个 token 都无效;
  3. 重新请求获取 token 的过程中会有短暂 token 不可用的情况(可以通过在客户端设置定时器,当 accessToken 快过期的时候,提前去通过 refreshToken 获取新的 accessToken)。

4、Redis+JWT

如果将上述的方案合二为一

[!note] 登录流程

  1. 登录校验成功之后,将用户信息存储到 Redis 中以 userId 作为 key。
  2. 然后基于用户信息用 JWT 生成 token 字符串返回给前端
  3. 拦截器在拦截请求的时候需要判断 token 是否可以解析,然后再判断 Redis 中是否有值。

此时,如果用户盗刷登录接口重复登录的话,由于 Redis 中不能存储相同的 key 所以会抛出异常,而不会将相同用户信息存储多份,不会导致内存内存爆满。
然后因为 Redis 中也存储了用户登录的状态,所以此时我们可以手动将用户的登录信息移除,解决了单独使用 JWT 导致的无法退出登录的问题。

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

东莞呵呵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值