基于Redis实现短信验证码登录

目录

1 基于Session实现短信验证码登录

2 配置登录拦截器

3 配置完拦截器还需将自定义拦截器添加到SpringMVC的拦截器列表中 才能生效

4 Session集群共享问题

5 基于Redis实现短信验证码登录

6 Hash 结构与 String 结构类型的比较

7 Redis替代Session需要考虑的问题

8 基于redis短信验证登录

9 配置登录拦截器


1 基于Session实现短信验证码登录

    /*** 发送验证码*/@Overridepublic Result sendCode(String phone, HttpSession session) {// 1、判断手机号是否合法if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式不正确");}// 2、手机号合法,生成验证码,并保存到Session中String code = RandomUtil.randomNumbers(6);session.setAttribute(SystemConstants.VERIFY_CODE, code);// 3、发送验证码log.info("验证码:{}", code);return Result.ok();}/*** 用户登录*/@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {String phone = loginForm.getPhone();String code = loginForm.getCode();// 1、判断手机号是否合法if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式不正确");}// 2、判断验证码是否正确String sessionCode = (String) session.getAttribute(LOGIN_CODE);if (code == null || !code.equals(sessionCode)) {return Result.fail("验证码不正确");}// 3、判断手机号是否是已存在的用户User user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getPassword, phone));if (Objects.isNull(user)) {// 用户不存在,需要注册user = createUserWithPhone(phone);}// 4、保存用户信息到Session中,便于后面逻辑的判断(比如登录判断、随时取用户信息,减少对数据库的查询)session.setAttribute(LOGIN_USER, user);return Result.ok();}/*** 根据手机号创建用户*/private User createUserWithPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));this.save(user);return user;}

2 配置登录拦截器

public class LoginInterceptor implements HandlerInterceptor {/*** 前置拦截器,用于判断用户是否登录*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession();// 1、判断用户是否存在User user = (User) session.getAttribute(LOGIN_USER);if (Objects.isNull(user)){// 用户不存在,直接拦截response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);return false;}// 2、用户存在,则将用户信息保存到ThreadLocal中,方便后续逻辑处理// 比如:方便获取和使用用户信息,session获取用户信息是具有侵入性的ThreadLocalUtls.saveUser(user);return HandlerInterceptor.super.preHandle(request, response, handler);}
}

3 配置完拦截器还需将自定义拦截器添加到SpringMVC的拦截器列表中 才能生效

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 添加登录拦截器registry.addInterceptor(new LoginInterceptor())// 设置放行请求.excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/upload/**","/voucher/**");}
}

4 Session集群共享问题

(1)什么是Session集群共享问题

在分布式集群环境中,会话(Session)共享是一个常见的挑战。默认情况下,Web 应用程序的会话是保存在单个服务器上的,当请求不经过该服务器时,会话信息无法被访问。

(2) Session集群共享问题造成哪些问题

 服务器之间无法实现会话状态的共享。比如:在当前这个服务器上用户已经完成了登录,Session中存储了用户的信息,能够判断用户已登录,但是在另一个服务器的Session中没有用户信息,无法调用显示没有登录的服务器上的服务

 (3)如何解决Session集群共享问题

方案一:Session拷贝(不推荐)

Tomcat提供了Session拷贝功能,通过配置Tomcat可以实现Session的拷贝,但是这会增加服务器的额外内存开销,同时会带来数据一致性问题

方案二:Redis缓存(推荐)

Redis缓存具有Session存储一样的特点,基于内存、存储结构可以是key-value结构、数据共享

(4)Redis缓存相较于传统Session存储的优点

1 高性能和可伸缩性:Redis 是一个内存数据库,具有快速的读写能力。相比于传统的 Session 存储方式,将会话数据存储在 Redis 中可以大大提高读写速度和处理能力。此外,Redis 还支持集群和分片技术,可以实现水平扩展,处理大规模的并发请求。
2 可靠性和持久性:Redis 提供了持久化机制,可以将内存中的数据定期或异步地写入磁盘,以保证数据的持久性。这样即使发生服务器崩溃或重启,会话数据也可以被恢复。
3 丰富的数据结构:Redis 不仅仅是一个键值存储数据库,它还支持多种数据结构,如字符串、列表、哈希、集合和有序集合等。这些数据结构的灵活性使得可以更方便地存储和操作复杂的会话数据。
4 分布式缓存功能:Redis 作为一个高效的缓存解决方案,可以用于缓存会话数据,减轻后端服务器的负载。与传统的 Session 存储方式相比,使用 Redis 缓存会话数据可以大幅提高系统的性能和可扩展性。
5 可用性和可部署性:Redis 是一个强大而成熟的开源工具,有丰富的社区支持和活跃的开发者社区。它可以轻松地与各种编程语言和框架集成,并且可以在多个操作系统上运行。
PS:但是Redis费钱,而且增加了系统的复杂度

5 基于Redis实现短信验证码登录

 

Hash 结构与 String 结构类型的比较

  • String 数据结构是以 JSON 字符串的形式保存,更加直观,操作也更加简单,但是 JSON 结构会有很多非必须的内存开销,比如双引号、大括号,内存占用比 Hash 更高
  • Hash 数据结构是以 Hash 表的形式保存,可以对单个字段进行CRUD,更加灵活

 7 Redis替代Session需要考虑的问题

(1)选择合适的数据结构,了解 Hash 比 String 的区别
(2)选择合适的key,为key设置一个业务前缀,方便区分和分组,为key拼接一个UUID,避免key冲突防止数据覆盖
(3)选择合适的存储粒度,对于验证码这类数据,一般设置TTL为3min即可,防止大量缓存数据的堆积,而对于用户信息这类数据可以稍微设置长一点,比如30min,防止频繁对Redis进行IO操作

 8 基于redis短信验证登录

    /*** 发送验证码** @param phone* @param session* @return*/@Overridepublic Result sendCode(String phone, HttpSession session) {// 1、判断手机号是否合法if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式不正确");}// 2、手机号合法,生成验证码,并保存到Redis中String code = RandomUtil.randomNumbers(6);stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code,RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);// 3、发送验证码log.info("验证码:{}", code);return Result.ok();}/*** 用户登录** @param loginForm* @param session* @return*/@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {String phone = loginForm.getPhone();String code = loginForm.getCode();// 1、判断手机号是否合法if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式不正确");}// 2、判断验证码是否正确String redisCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);if (code == null || !code.equals(redisCode)) {return Result.fail("验证码不正确");}// 3、判断手机号是否是已存在的用户User user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));if (Objects.isNull(user)) {// 用户不存在,需要注册user = createUserWithPhone(phone);}// 4、保存用户信息到Redis中,便于后面逻辑的判断(比如登录判断、随时取用户信息,减少对数据库的查询)UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);// 将对象中字段全部转成string类型,StringRedisTemplate只能存字符串类型的数据Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));String token = UUID.randomUUID().toString(true);String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);return Result.ok(token);}/*** 根据手机号创建用户并保存** @param phone* @return*/private User createUserWithPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));this.save(user);return user;}

9 配置登录拦截器

单独配置一个拦截器用户刷新Redis中的token:在基于Session实现短信验证码登录时,我们只配置了一个拦截器,这里需要另外再配置一个拦截器专门用于刷新存入Redis中的 token,因为我们现在改用Redis了,为了防止用户在操作网站时突然由于Redis中的 token 过期,导致直接退出网站,严重影响用户体验。那为什么不把刷新的操作放到一个拦截器中呢,因为之前的那个拦截器只是用来拦截一些需要进行登录校验的请求,对于哪些不需要登录校验的请求是不会走拦截器的,刷新操作显然是要针对所有请求比较合理,所以单独创建一个拦截器拦截一切请求,刷新Redis中的Key

 登录拦截器:

public class LoginInterceptor implements HandlerInterceptor {/*** 前置拦截器,用于判断用户是否登录*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 判断当前用户是否已登录if (ThreadLocalUtls.getUser() == null){// 当前用户未登录,直接拦截response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);return false;}// 用户存在,直接放行return true;}
}

刷新token的拦截器

public class RefreshTokenInterceptor implements HandlerInterceptor {// new出来的对象是无法直接注入IOC容器的(LoginInterceptor是直接new出来的)// 所以这里需要再配置类中注入,然后通过构造器传入到当前类中private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1、获取token,并判断token是否存在String token = request.getHeader("authorization");if (StrUtil.isBlank(token)){// token不存在,说明当前用户未登录,不需要刷新直接放行return true;}// 2、判断用户是否存在String tokenKey = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);if (userMap.isEmpty()){// 用户不存在,说明当前用户未登录,不需要刷新直接放行return true;}// 3、用户存在,则将用户信息保存到ThreadLocal中,方便后续逻辑处理,比如:方便获取和使用用户信息,Redis获取用户信息是具有侵入性的UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);ThreadLocalUtls.saveUser(BeanUtil.copyProperties(userMap, UserDTO.class));// 4、刷新token有效期stringRedisTemplate.expire(token, LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}
}

将自定义的拦截器添加到SpringMVC的拦截器表中,使其生效:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {// new出来的对象是无法直接注入IOC容器的(LoginInterceptor是直接new出来的)// 所以这里需要再配置类中注入,然后通过构造器传入到当前类中@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 添加登录拦截器registry.addInterceptor(new LoginInterceptor())// 设置放行请求.excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/upload/**","/voucher/**").order(1); // 优先级默认都是0,值越大优先级越低// 添加刷新token的拦截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}
  • RefreshTokenInterceptor 先执行,主要用来检查 token 的有效性并刷新 token 的有效期,同时将用户信息存入 ThreadLocal
  • LoginInterceptor 后执行,验证 ThreadLocal 中是否有用户信息,以确认用户是否登录。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/5471.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Golang Gin系列-4:Gin Framework入门教程

在本章中&#xff0c;我们将深入研究Gin&#xff0c;一个强大的Go语言web框架。我们将揭示制作一个简单的Gin应用程序的过程&#xff0c;揭示处理路由和请求的复杂性。此外&#xff0c;我们将探索基本中间件的实现&#xff0c;揭示精确定义路由和路由参数的技术。此外&#xff…

Asp.Net Core 8.0 使用 Serilog 按日志级别写入日志文件的两种方式

1、所需的Nuget包 本文项目的版本是.NET 8.0&#xff0c;如果使用其它版本安装适配版本即可。 Serilog.AspNetCore(8.0.2) Serilog.Sinks.File(5.0.0) Serilog.Expressions(5.0.0) 2、两种配置方式 2.1 代码形式&#xff08;Program.cs&#xff09; 在Program.cs文件中&am…

STM32+W5500+以太网应用开发+003_TCP服务器添加OLED(u8g2)显示状态

STM32W5500以太网应用开发003_TCP服务器添加OLED&#xff08;u8g2&#xff09;显示状态 实验效果3-TCP服务器OLED1 拷贝显示驱动代码1.1 拷贝源代码1.2 将源代码添加到工程1.3 修改代码优化等级1.4 添加头文件路径1.5 修改STM32CubeMX工程 2 修改源代码2.1 添加头文件2.2 main函…

【深度学习项目】语义分割-DeepLab网络(DeepLabV3介绍、基于Pytorch实现DeepLabV3网络)

文章目录 介绍深度学习语义分割的关键特点主要架构和技术数据集和评价指标总结 DeepLabDeepLab 的核心技术DeepLab 的发展历史DeepLab V3网络结构获取多尺度信息架构Cascade ModelASPP ModelMulti-GridPytorch官方实现的DeepLab V3该项目主要是来自pytorch官方torchvision模块中…

Golang Gin系列-5:数据模型和数据库

在这篇Gin教程的博客中&#xff0c;我们将探索如何将模型和数据库与Gin框架无缝集成&#xff0c;使你能够构建健壮且可扩展的web应用程序。通过利用流行的库并遵循最佳实践&#xff0c;你将学习如何定义模型、建立数据库连接、执行CRUD操作以及确保基于gin的项目中的数据完整性…

高并发内存池_各层级的框架设计及ThreadCache(线程缓存)申请内存设计

一、高并发内存池框架设计 高并发池框架设计&#xff0c;特别是针对内存池的设计&#xff0c;需要充分考虑多线程环境下&#xff1a; 性能问题锁竞争问题内存碎片问题 高并发内存池的整体框架设计旨在提高内存的申请和释放效率&#xff0c;减少锁竞争和内存碎片。 高并发内存…

如何在idea中搭建SpringBoot项目

如何在idea中快速搭建SpringBoot项目 目录 如何在idea中快速搭建SpringBoot项目前言一、环境准备&#xff1a;搭建前的精心布局 1.下载jdk &#xff08;1&#xff09;安装JDK&#xff1a;&#xff08;2&#xff09;运行安装程序&#xff1a;&#xff08;3&#xff09;设置安装…

elementUI Table组件实现表头吸顶效果

需求描述 当 table 内容过多的时候&#xff0c;页面上滑滚动&#xff0c;表头的信息也会随着被遮挡&#xff0c;无法将表头信息和表格内容对应起来&#xff0c;需要进行表头吸顶 开始编码&#x1f4aa; 环境&#xff1a;vue2.6、element UI step1&#xff1a; 给el-table__h…

[Qt]系统相关-多线程、线程安全问题以及线程的同步机制

目录 一、Qt多线程编程 1.介绍 2.多线程的操作 线程的创建 QThread的常用API 使用案例 3.Qt线程的使用场景 二、线程安全问题 1.互斥锁 介绍 使用案例 2.读写锁 三、线程的同步 1.条件变量 2.信号量 一、Qt多线程编程 1.介绍 Qt中的多线程的底层原理和注意事项…

Linux Bash 中使用重定向运算符的 5 种方法

注&#xff1a;机翻&#xff0c;未校。 Five ways to use redirect operators in Bash Posted: January 22, 2021 | by Damon Garn Redirect operators are a basic but essential part of working at the Bash command line. See how to safely redirect input and output t…

【Linux】环境变量

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;Linux &#x1f339;往期回顾&#x1f339;&#xff1a;【Linux】进程优先级与进程切换 &#x1f516;流水不争&#xff0c;争的是滔滔不 一、环境变量的定义二、命令…

Spring MVC:设置响应

目录 引言 1. 返回静态页面 1.1 Spring 默认扫描路径 1.2 RestController 1.2.1 Controller > 返回页面 1.2.2 ResponseBody 2. 返回 HTML 2.1 RequestMapping 2.1.1 produces(修改响应的 Content-Type) 2.1.2 其他属性 3. 返回 JSON 4. 设置状态码 4.1 HttpSer…

开篇:吴恩达《机器学习》课程及免费旁听方法

课程地址&#xff1a; Machine Learning | Coursera 共包含三个子课程 Supervised Machine Learning: Regression and Classification | Coursera Advanced Learning Algorithms | Coursera Unsupervised Learning, Recommenders, Reinforcement Learning | Coursera 免费…

【C++】模板(进阶)

本篇我们来介绍更多关于C模板的知识。模板初阶移步至&#xff1a;【C】模板&#xff08;初阶&#xff09; 1.非类型模板参数 1.1 非类型模板参数介绍 模板参数可以是类型形参&#xff0c;也可以是非类型形参。类型形参就是我们目前接触到的一些模板参数。 //类型模板参数 …

快手SDK接入错误处理经验总结(WebGL方案)

1、打包时提示Assets\WebGLTemplates\ks路径下未找到Index.html文件错误 处理方法&#xff1a;直接使用Unity默认模板下的Index.html文件即可 文件所在路径&#xff1a;Unity安装路径\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\WebGLTemplates\Default 参考图&a…

用edge浏览器追剧音量太小?安装音量增强器可解忧

0 源起 春节佳节将至&#xff0c;可以利用这个难得的假期追一追想看而没空看的剧了。 但是在用Edge浏览器播放网页中的视频时&#xff0c;有时音量太小&#xff0c;根本没法听清楚&#xff0c; 遇到这种情况时&#xff0c;尽管Edge浏览器本身没有提供音量控制功能&#xff0…

Alluxio 联手 Solidigm 推出针对 AI 工作负载的高级缓存解决方案

作者&#xff1a;Wayne Gao, Yi Wang, Jie Chen, Sarika Mehta Alluxio 作为全球领先的 AI 缓存解决方案供应商&#xff0c; 提供针对 GPU 驱动 AI 负载的高速缓存。其可扩展架构支持数万个节点&#xff0c;能显著降低存储带宽的消耗。Alluxio 在解决 AI 存储挑战方面的前沿技…

Excel 技巧15 - 在Excel中抠图头像,换背景色(★★)

本文讲了如何在Excel中抠图头像&#xff0c;换背景色。 1&#xff0c;如何在Excel中抠图头像&#xff0c;换背景色 大家都知道在PS中可以很容易抠图头像&#xff0c;换背景色&#xff0c;其实Excel中也可以抠简单的图&#xff0c;换背景色。 ※所用头像图片为百度搜索&#x…

JavaScript笔记基础篇03——函数

黑马程序员视频地址&#xff1a;黑马程序员前端JavaScript入门到精通全套视频教程https://www.bilibili.com/video/BV1Y84y1L7Nn?vd_source0a2d366696f87e241adc64419bf12cab&spm_id_from333.788.videopod.episodes 目录 函数 函数的使用 1.函数的声明语法 2.函数的…

manim(manimgl)安装教学-win11(2024-08)

manim 目前的两种版本&#xff1a;★★ 稍微捋一捋【项目中的 readme.md 十分重要】 manimgl 是 Grant Sanderson&#xff08;YouTube频道 3Blue1Brown的作者&#xff09;等人开发。 现在为 manimgl&#xff0c;在维护中。 manimCE 是2020年后的 manim 分支 manim community e…