黑马点评登录设计

短信验证码登录
- 登录界面输入手机号,然后点获取验证码。此时后端首先校验手机号格式是否正确,接着生成一个验证码。把手机号和对应的验证码存入 Redis,设置过期时间为 2 分钟。
- 登录界面填写验证码,然后点击登录。此时后端对比传过来的手机号和验证码。如果一致,则进行下一步。
- 如果该用户不存在,则创建用户并且保存用户到数据库。
- 随机生成token,作为登录令牌;并将 User 对象转成 Hash,以 token 为 key,以 hash为 value,存入 Redis。
校验登录状态
- 前端携带的请求会带有 token,所有请求都会被RefreshTokenInterceptor拦截。如果没有token直接放行
- 首先获取请求头中的 token,然后基于token获取redis中存储的用户(一个 Map对象)。
- 如果用户存在,则将Map对象转换为UserDTO,并且把它存储在ThreadLocal中,并且刷新 token 的有效期。
第二个Interceptor:除了指定的(不需要用户登陆的)路径之外,其它请求路径都会被 LoginInterceptor 拦截,如果此时没有登录,(UserHolder里为空—),则无法处理请求。
UserHolder
每个 ThreadLocal 可以存放一个对象,同一个线程的所有ThreadLocal都存在该线程私有的ThreadLocalMap中。
通过ThreadLocal的 get, set, remove方法,我们可以充分利用该线程的用户信息。
| |
一个需要登录的请求线程完成工作之后,loginInterceptor里面定义的afterCompletion方法会把该用户从UserHolder中移除。
Q: 它会根据这个 token 做什么?是否会判断 token 是否过期?
准确地说:
它不会判断 JWT 是否过期(你用的是 Redis 存 token 的话,token 是个字符串,不是自包含的 JWT,token 本身没有过期概念);
它的判断逻辑是:
从请求头读取 token
拿 token 去 Redis 查找对应的用户哈希信息
如果 Redis 有值 → 用户在线,调用 UserHolder.set(userDTO) 存入 ThreadLocal,并刷新 Redis 的 TTL
如果 Redis 没有这个 token(过期了/被删除) → 不做任何操作,让请求继续往下走
Q: 如果 token 过期了,会怎样?
如果 token 对应的 Redis 键找不到了(例如因为 TTL 到期被自动删除),流程是这样的:
RefreshTokenInterceptor 无法从 Redis 获取用户信息 → 不设置 ThreadLocal
请求继续往下走,到 LoginInterceptor
LoginInterceptor 检查 UserHolder.get(),发现是 null
→ 拦截请求,返回 “请登录” 提示