• 基于Session实现短信登录


    目录

    一、基于Session实现登录

    1.1 业务流程图​编辑

    二、发送短信验证码

    2.1 发送短信请求方式及参数说明

    2.2 业务层代码模拟发送短信

    三、登录功能

     3.1  短信验证的请求方式及路径

    3.2  业务层代码实现用户登录

    3.3 拦截器——登录验证功能

    三、隐藏用户敏感信息

    四、session共享问题


    一、基于Session实现登录、校验

    1.1 业务流程图

    在校验登录状态步骤时为什么会有Cookie的操作?

       session就是基于Cookie的,每一个session都会有一个对应的id保存在浏览器的cookie之中,当浏览器发送请求的时候一定会携带cookie,那也携带对应的session的id,那这样的话我们就可以在服务端代码中获取到对应的session,从而就能从session当中获取用户

    ThreadLocal是什么?

       是一个线程域对象,每一个请求到达我们的服务都是一个独立线程

       如果我们没有使用ThreadLocal而是直接把用户保存到一个本地变量,此时会出现多线程并发修改的一个安全问题

      ThreadLocal会将数据保存到每一个线程的内部,在线程内部创建一个Map来去保存,这样以来每个线程都有自己独立的存储空间,那每一个请求来了之后都去到各自的空间,相互之间没有干扰

    下面这个文章有对Cookie和Session的解释

    cookie&session使用方式 - changeyi - 博客园 (cnblogs.com)

    下面这篇文章中也有对Cookie与Session使用的演示

     SpringMVC_我爱布朗熊的博客-CSDN博客

    二、发送短信验证码

    2.1 发送短信请求方式及参数说明

    这个地方为什么需要session?  因为我们需要把验证码保存在session当中

    1. /**
    2. * 发送手机验证码
    3. */
    4. @PostMapping("code")
    5. public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
    6. // TODO 发送短信验证码并保存验证码
    7. // return Result.fail("功能未完成");
    8. return userService.sendCode(phone,session);
    9. }

    2.2 业务层代码模拟发送短信

    1. @Override
    2. public Result sendCode(String phone, HttpSession session) {
    3. // 1.校验手机号
    4. if(RegexUtils.isPhoneInvalid(phone)){
    5. // 说明:RegexUtils使我们封装的一个类 isCodeInvalid是里面的静态方法,在这个静态方法里面又调用了另外一个静态方法得以实现
    6. // 2.如果不符合,返回错误信息
    7. return Result.fail("手机号格式错误");
    8. }
    9. // 3.符合,生成验证码 6代表生成的验证码的长度 RandomUtil使用这个工具类生成
    10. String code = RandomUtil.randomNumbers(6);
    11. // 4.保存验证码到session key必须是一个字符串,value是一个对象
    12. session.setAttribute("code",code);
    13. // 5.发送验证码
    14. // 实现起来比较麻烦 我们使用日志假装发送
    15. log.debug("发送短信验证码成功,验证码:"+code);
    16. return Result.ok();
    17. }
    18. }

    三、登录功能

     3.1  短信验证的请求方式及路径

    1. /**
    2. * 登录功能
    3. * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
    4. */
    5. @PostMapping("/login")
    6. public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
    7. // TODO 实现登录功能
    8. return userService.login(loginForm,session);
    9. }

    3.2  业务层代码实现用户登录

    流程图:

    代码:

    1. /**
    2. * 实现用户登录
    3. * @param loginForm 登录的参数
    4. * @param session
    5. * @return
    6. */
    7. @Override
    8. public Result login(LoginFormDTO loginForm, HttpSession session) {
    9. // 1.校验手机号
    10. if(RegexUtils.isPhoneInvalid(loginForm.getPhone())){
    11. // 说明:RegexUtils使我们封装的一个类 isCodeInvalid是里面的静态方法,在这个静态方法里面又调用了另外一个静态方法得以实现
    12. // 1.2.如果不符合,返回错误信息
    13. return Result.fail("手机号格式错误");
    14. }
    15. // 2.校验验证码
    16. // 2.1 得到code 这个值是真实的code
    17. Object cacheCode = session.getAttribute("code");
    18. // 2.2 获取用户输入的code
    19. String code = loginForm.getCode();
    20. if(cacheCode ==null || !cacheCode.toString().equals(code)){
    21. // 3.不一致,报错
    22. return Result.fail("验证码错误");
    23. }
    24. // 4.一致,根据手机号查询用户 .one()代表查询一个 list()代表着查询多个
    25. User user =query().eq("phone",loginForm.getPhone()).one();
    26. // 5.判断用户是否存在
    27. if(user ==null){
    28. // 6.不存在,创建新用户并保存
    29. user = createUserWithPhone(loginForm.getPhone());
    30. }
    31. // 7.保存用户信息到session中
    32. session.setAttribute("user",user);
    33. return Result.ok();
    34. }
    35. private User createUserWithPhone(String phone) {
    36. // 1.创建用户
    37. User user = new User();
    38. user.setPhone(phone);
    39. // USER_NICK_NAME_PREFIX其实就是 "user_",这样写更有逼格
    40. user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
    41. // 保存用户
    42. save(user);
    43. return user;
    44. }

    3.3 拦截器——登录验证功能

    1. // HandlerInterceptor 这是一个拦截器
    2. public class LoginInterceptor implements HandlerInterceptor {
    3. // 前置拦截 在进入controller之前我们进行登录校验
    4. @Override
    5. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    6. // 1.获取session
    7. HttpSession session =request.getSession();
    8. // 2.获取session中的用户
    9. Object user = session.getAttribute("user");
    10. // 3.判断用户是否存在
    11. if(user == null){
    12. // 4.不存在,拦截
    13. response.setStatus(401); //返回401状态码
    14. return false;
    15. }
    16. // 5.存在,保存用户信息到ThreadLocal 保存在当前线程里面的
    17. UserHolder.saveUser((User)user);
    18. // 6.放行
    19. return true;
    20. }
    21. // 在controller执行之后拦截 这个我们在这里不需要
    22. // @Override
    23. // public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    24. // HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    25. // }
    26. // 渲染之后,返回给用户之前 用户业务执行完毕我们要销毁维护信息,避免泄露
    27. @Override
    28. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    29. // 移除用户
    30. UserHolder.removeUser();
    31. }
    32. }
    1. public class UserHolder {
    2. private static final ThreadLocal tl = new ThreadLocal<>();
    3. public static void saveUser(User user){
    4. tl.set(user);
    5. }
    6. public static User getUser(){
    7. return tl.get();
    8. }
    9. public static void removeUser(){
    10. tl.remove();
    11. }
    12. }
    1. @Configuration
    2. public class MvcConfig implements WebMvcConfigurer {
    3. // 拦截器的注册器
    4. @Override
    5. public void addInterceptors(InterceptorRegistry registry) {
    6. registry.addInterceptor(new LoginInterceptor())
    7. .excludePathPatterns(
    8. "/user/code",
    9. "/user/login",
    10. "/shop/**",
    11. "/blog/hot",
    12. "/shop-type/**",
    13. "upload/**",
    14. "voucher/**"
    15. );
    16. }
    17. }
    1. @GetMapping("/me")
    2. public Result me(){
    3. // TODO 获取当前登录的用户并返回
    4. // 直接取就可以了
    5. User user= UserHolder.getUser();
    6. return Result.ok(user);
    7. }

    三、隐藏用户敏感信息

    如下图所示,服务器返回的信息有点多,我们为了保护用户的信息,我们需要隐藏部分的内容

    所以一开始我们存入session的信息就不应该是完整的信息,这样才能降低服务器的压力

    UserServiceImpl中的login方法

    1. // 7.保存用户信息到session中 \
    2. // BeanUtil.copyProperties(user, UserDTO.class)) 会自动的将user中的属性拷贝到UserDTO当中而且也创建出一个UserDTO对象
    3. session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

    取的时候我们也应该做出变化

    LoginInterceptor类,相关地方也记得改一下
    1. // 5.存在,保存用户信息到ThreadLocal 保存在当前线程里面的
    2. UserHolder.saveUser((UserDTO)user);

    此时我们再登录查询信息,就还剩下三个字段了

    四、session共享问题

    多台Tomcat并不共享session存储空间,当请求切换到不同的Tomcat服务导致数据丢失的问题

    所以这个方案就被pass了

    session的替代方案应该满足:

    • 数据共享
    • 内存存储
    • key、value结构

    所以我们选择Redis

    任何一台Tomcat都能访问到Redis,这样就能实现数据共享

    有兴趣的朋友可以再对比一下Redis实现短信登录,两个对比学习

    Redis替代Session实现用户短信登录(超级详细解释)_我爱布朗熊的博客-CSDN博客

  • 相关阅读:
    英语词典缩略词
    SpringBoot中CommandLineRunner的使用
    java毕业设计校园博客系统mybatis+源码+调试部署+系统+数据库+lw
    使用rustc_interface进行类型检查
    史上最短苹果发布会;三星、LG、高通联手进军 XR 市场丨 RTE 开发者日报 Vol.74
    iOS开发Swift-16-App的生命周期-AppDelegate和SceneDelegate
    2023高教社杯数学建模C题思路模型 - 蔬菜类商品的自动定价与补货决策
    flink中不同序列化器性能对比
    剪辑的视频太大怎么办?一分钟学会压缩视频
    使用UDP协议实现简单的分布式日志服务, java和python
  • 原文地址:https://blog.csdn.net/weixin_51351637/article/details/127519489