登录模块设计| 青训营

397 阅读6分钟

对于登录操作,本文章将会从基于session实现登录出发,分析出现的问题并最终构建一个基于redis实现共享的session登录。

1.基于session实现登陆

完整的session登陆中包含三块业务逻辑:发送短信验证码、验证码登陆/注册、校验登录状态。

image.png

Note. 关于HttpSession的补充知识:

  • HttpSession 是服务端的技术,服务器会为每一个用户创建一个独立的HttpSession
  • HttpSession原理

(1)当用户第一次访问Servlet时,服务器端会给用户创建一个独立的Session。并且生成一个SessionID,这个SessionID在响应浏览器的时候会被装进cookie中,从而被保存到浏览器中;

(2)当用户再一次访问Servlet时,请求中会携带着cookie中的SessionID去访问;服务器会根据这个SessionID去查看是否有对应的Session对象,有就拿出来使用;没有就创建一个Session(相当于用户第一次访问)问)

  • 域的范围:

Context域 > Session域 > Request域 ​ Session域只要会话不结束就会存在 但是Session有默认的存活时间(30分钟)

(1)发送短信验证码

功能的触发如下图所示,会携带参数对/user/code路径发起post请求,接下来我们将会完善这段对应的后端逻辑:

image.png 后端逻辑流程如下:

  • 校验手机号,对正确的手机号发送短信验证码,并将验证码存储到该提交表单servlet的session中暂存;
  • 不正确的手机号返回失败。

(2)验证码登陆/注册

在这个部分需要对正确的手机号校验验证码,对通过校验的用户从数据库查询已有或新建,并将User信息存储到session中。其中,如果如果手机号未注册则需要新建用户:

  • 新建用户存储手机号;
  • 随机生成新用户昵称(其中用户的初始名称可以用统一前缀定义);
  • 在数据库中增加新用户。

(3)校验登录状态

  • 问题:校验登陆不止在login登陆跳转个人界面后会用到,在许多逻辑后都需要校验登陆。因此需要在上面校验登陆状态的流程图之外,通过其他方式满足登陆校验的服用;可以使用拦截器来完成这一点,在拦截其中做登录校验的逻辑,通过后放行到不同的controller中,如下图: image.png
  • 当手机号登录/注册的时候,会自动调用拦截器进行登录状态校验
  • 用户通过拦截器的校验成功登陆后,就将个人信息还在后的页面返回给用户

2.分析集群的session共享问题

  • 问题:由于实际中会使用多台 tomcat 分布式部署,Nginx会进行负载均衡随机使用其中的 tomcat,而问题在于tomcat之间不共享存储空间,所以有时请求因为负载均衡切换到了不同的 tomcat 中会找不到 session,导致数据丢失的问题。

image.png

  • 分析:替代方案需要解决集群 session 数据丢失问题,同时满足如下的要求:

(1)需要让 session 在分布式集群中共享

(2)需要也在内存中存储,保证高效频繁的登录校验

(3)最好使用 key-value 的简单高效存储结构

  • 方案:使用 Redis 来代替使用 session 在 tomcat 中存储,更详细的复杂流程将会在下一节详细描述。

3.基于Redis实现共享session登陆

(1)数据类型选择:

  • 不使用servlet中的session,将 code 保存在 Redis 中,需要选择数据结构的类型;有String结构以json存储、Hash结构以字段存储两者可做选择;
  • 选hash结构。因为只需要存 phone 和 code,hash结构内存占用少,将对象中的单个字段做CRUD而不用像json 一样改整体。

(2)存储的key-value选择:

  • session中 key 为 code,但 Redis 中不能用 code 作为 key。因为 session 在 tomcat 中彼此是独立的,即使是随机到了相同的 code 也相互不会干扰;但每个code随机生成是在分布的 tomcat 中的,而 Redis 只有一个,code 可能会重复。那么可以用手机号 String phone 为 key 存储验证码。
  • 进一步分析,phone 可能会泄露用户信息,我么可以再改一下使用一个 Redis 生成的随机tocken 作为 key,用户数据作为 value。
  • 但进一步的,token不会像session一样,自动被浏览器写到cookie中,需要我们自己写入。

image.png

(3)用 Redis 实现 session 登录的业务流程

接下里将会对:发送短信验证码、验证码登陆/注册、校验登录状态这三个部分分别进行 Redis 改造。

1)基于 Redis 发送短信验证码

  • 将之前的业务逻辑中,使用 session 存 code 换成用 Redis 存 code:
  • 可以使用redis的set方法存储数据,为了和其他业务区分开,存储室需要在key中加入所属业务的前缀,同时设置保存时间。

2)基于 Redis 验证码登陆/注册

  • 验证登录把从 session 获取用户变成从 redis 获取,生成随机 token 将登陆用户信息存储到 Redis 中;
  • 使用和set时相同的字段从redis中取出code,其余和之前的方案相同;
  • 最后为 token 增加有效期。

接下里来还有点问题,我们设置的有效期是在登陆时计时,过 30min 就结束了;而我们希望用户访问后这个TTL会刷新,所以还需要做一些工作。我们可以在拦截器中取出请求头的 token ,用 token 在 redis 中查询用户,如果比对通过代表此时有一个已登录未过期用户正在操作页面,我们此时在拦截器中刷新TTL,就可以解决上述问题。

image.png

3)登录拦截器的优化 首先回顾一下上述已经完成的业务逻辑:

image.png

  • 问题:最后一个问题是,我们之前设置的拦截器只拦截需要登录状态的操作。那么也就是说非登录状态的操作不经过这个拦截器,也就无法完成状态的刷新。
  • 方案:再增加一个拦截器,拦截一切路径;然后把之前拦截器的刷新操作交给新增拦截器完成,之前的那个拦截器只需要查询ThreadLocal中的用户进行更新。

image.png

综上所述,我们就完成了整个使用Redis进行的短信登录模块构造。