Redis实战——短信登录(二)

news2024/11/22 13:22:25

Redis代替session

  • redis中设计key
    • 在使用session时,每个用户都会有自己的session,这样虽然验证码的键都是“code”,但是相互不影响,从而确保每个用户获取到的验证码只能够自己使用,当使用redis时,redis的key是共享的,不分用户,就要求在redis中存储验证码时,不能直接将验证码的键设置为"code",这样无法保证其唯一性。
  • redis中设计value
    • 到底该使用redis中什么数据类型存储数据,主要需要看数据样式和使用方式,一般会考虑使用String、Hash,String存储时,会多占一点内存空间,则相对来说Hash存储时,会少占用一点内存空间。
      • String结构:以Json字符串来存储,比较直观
      • Hash结构:,每个对象中每个字段独立存储,可以针对单个字段做CRUD

redis实现登录

发送验证码
发送验证码-redis
发送验证码-redis
  • 添加redis

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    
  • 设置redis的连接信息

    spring:
      redis:
        host: 192.168.175.128
        port: 6379
        password: liang
        lettuce:
          pool:
            max-active: 10
            max-idle: 10
            min-idle: 1
            time-between-eviction-runs: 10s
  • 增加相关常量

    /**
     * 保存验证码的redis中的key
     */

    public static final String LOGIN_CODE_KEY = "login:code:";
    /**
     * 验证码的过期时间
     */

    public static final Long LOGIN_CODE_TTL = 2L;
  • 修改Service层

       @Autowired
        StringRedisTemplate stringRedisTemplate;

        @Override
        public boolean sendCode(String phone, HttpSession session) {
            //获取手机号,验证手机号是否合规
            boolean mobile = PhoneUtil.isMobile(phone);
            //不合规,则提示
            if (!mobile){
                return false;
            }
            //生成验证码
            String code = RandomUtil.randomNumbers(6);
            //保存验证码到redis,并设置过期时间
            stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
            //发送验证码,这里就通过打印验证码模拟了下发送验证码
            System.out.println("验证码:" + code);
            return true;
        }
  • 修改Controller层

    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        String uuid = userService.sendCode(phone, session);
        return uuid.equals("") ? Result.fail("手机号码不合规"): Result.ok(uuid);
    }
验证码登录、注册
验证码登录注册-redis
验证码登录注册-redis
  • 增加相关常量

    public static final String LOGIN_USER_KEY = "login:token:";
    public static final Long LOGIN_USER_TTL = 30L;
  • 修改Service层

    @Override
    public String login(LoginFormDTO loginForm, HttpSession session) {
        //获取手机号
        String phone = loginForm.getPhone();
        //验证手机号是否合理
        boolean mobile = PhoneUtil.isMobile(phone);
        //如果不合理 提示
        if (!mobile){
            //提示用户手机号不合理
            return "";
        }
        //手机号合理 进行验证码验证
        String code = loginForm.getCode();
        //从redis中获取验证码
        String redisCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        //如果验证码输入的是错误的  提示
        if (!code.equals(redisCode)){
            return "";
        }
        //如果验证码也正确 那么通过手机号进行查询
        User user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
        // 数据库中没查询到用户信息
        if (ObjectUtil.isNull(user)){
            user = new User();
            user.setPhone(phone);
            user.setNickName("user_"+ RandomUtil.randomString(10));
            this.save(user);
        }
        // 将用户信息保存到Redis中,注意避免保存用户敏感信息
        UserDTO userDTO = BeanUtil.toBean(user, UserDTO.class);
        // 设置UUID保存用户信息
        String uuid = IdUtil.fastSimpleUUID();
        // 将user对象转化为Map,同时将Map中的值存储为String类型的
        Map<String, Object> userDTOMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create().ignoreNullValue()
                        .setFieldValueEditor((key, value) -> value.toString()));
        stringRedisTemplate.opsForHash().putAll( LOGIN_USER_KEY + uuid, userDTOMap);
        //设置过期时间
        stringRedisTemplate.expire(LOGIN_USER_KEY + uuid, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 通过UUID生成简单的token
        String token = uuid + userDTO.getId();
        return token;
    }    
    String login(LoginFormDTO loginForm, HttpSession session);
  • 修改Controller层

    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        String token = userService.login(loginForm, session);
        return StrUtil.isNotBlank(token) ? Result.ok(token) : Result.fail("手机号或验证码错误");
    }
校验登录状态
校验登录状态-redis
校验登录状态-redis
  • 修改LoginInterceptor拦截器

    private StringRedisTemplate stringRedisTemplate;

    /**
     * 构造函数
     * @param stringRedisTemplate
     */

    public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * preHandle方法的返回值决定是否放行,该方法在控制层方法执行前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //从请求头中获取token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)){
            return false;
        }

        String uuid = token.substring(0,token.lastIndexOf("-"));
        System.out.println(uuid);
        //从redis中获取值
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + uuid);
        if (ObjectUtil.isNull(entries)){
            return false;
        }
        //将map转化为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), true);
        //将用户信息保存到 ThreadLocal
        UserHolder.saveUser(userDTO);
        return true;
    }
    @Configuration
    public class MvcConfig implements WebMvcConfigurer {

        @Resource
        StringRedisTemplate stringRedisTemplate;

        /**
         * 添加拦截器
         * @param registry
         */

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //添加拦截器
            registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
                    //放行资源
                    .excludePathPatterns(
                            "/shop/**",
                            "/voucher/**",
                            "/shop-type/**",
                            "/upload/**",
                            "/blog/hot",
                            "/user/code",
                            "/user/login"
                    )
                    // 设置拦截器优先级
                    .order(1);
        }
    }
登录状态的刷新问题
  • 因为设置了redis中存储的用户的有效期,所以在用户访问界面的时,需要更新token令牌的存活时间,例如修改LoginInterceptor拦截器,在此拦截器中刷新过期时间

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //从请求头中获取token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)){
            return false;
        }

        String uuid = token.substring(0,token.lastIndexOf("-"));
        System.out.println(uuid);
        //从redis中获取值
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + uuid);
        if (ObjectUtil.isNull(entries)){
            return false;
        }
        //将map转化为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), true);
        //将用户信息保存到 ThreadLocal
        UserHolder.saveUser(userDTO);
        //刷新token有效期
        stringRedisTemplate.expire(LOGIN_USER_KEY + uuid, LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }
  • 但是需要注意的是,自定义的登录拦截器只是针对需要登录访问的请求进行了拦截,如果用户访问没被拦截的请求,该拦截器不会生效,则token令牌不能进行更新,当用户长时间访问不需要登录的页面,token令牌失效,再去访问被拦截的请求,则需要重新登录,这是不合理的。所有我们还需要在定义一个拦截器,进行token令牌刷新。

拦截器优化-redis
拦截器优化-redis
  • 刷新令牌的Interceptor

    /**
     * 刷新令牌的拦截器
     * @author liang
     */

    public class RefreshTokenInterceptor implements HandlerInterceptor {

        private StringRedisTemplate redisTemplate;

        public RefreshTokenInterceptor(StringRedisTemplate redisTemplate) {
            this.redisTemplate = redisTemplate;
        }

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

            //从请求头中获取token
            String token = request.getHeader("authorization");
            if (StrUtil.isBlank(token)){
                return false;
            }
            String uuid = token.substring(0, token.lastIndexOf("-"));
            //从Redis中获取值
            Map<Object, Object> userMap = redisTemplate.opsForHash().entries(LOGIN_USER_KEY + uuid);
            if (ObjectUtil.isNull(userMap)){
                return false;
            }
            //将map转换为UserDTO对象
            UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
            //将用户信息保存到 ThreadLocal
            UserHolder.saveUser(userDTO);
            //刷新token有效期
            redisTemplate.expire(LOGIN_USER_KEY + uuid, LOGIN_USER_TTL, TimeUnit.MINUTES);
            return true;

        }

        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            UserHolder.removeUser();
        }
    }
  • 修改登录的Interceptor

    public class LoginInterceptor implements HandlerInterceptor {

        /**
         * preHandle方法的返回值决定是否放行,该方法在控制层方法执行前执行
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            UserDTO user = UserHolder.getUser();
            return ObjectUtil.isNotNull(user);
        }
    }    
  • 修改WebMvcConfigurer配置类

    @Configuration
    public class MvcConfig implements WebMvcConfigurer {

        @Resource
        StringRedisTemplate stringRedisTemplate;

        /**
         * 添加拦截器
         * @param registry
         */

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //添加拦截器
            registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate));
            registry.addInterceptor(new LoginInterceptor())
                    //放行资源
                    .excludePathPatterns(
                            "/shop/**",
                            "/voucher/**",
                            "/shop-type/**",
                            "/upload/**",
                            "/blog/hot",
                            "/user/code",
                            "/user/login"
                    )
                    // 设置拦截器优先级
                    .order(1);
        }
    }

本文由 mdnice 多平台发布

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MCU单片机智能控制落地扇解决方案

随着科技进步和消费升级&#xff0c;消费者对电风扇的智能化需求日趋强烈。为了快速响应市场需求&#xff0c;以更低的成本打造智能电风扇。 MCU单片机智能控制落地扇解决方案以灵动微MM32SPIN040C主控。 方案采用的主控MM32SPIN040工作频率可达48MHz&#xff0c;内置16KB Fl…

虹科分享 | 拒绝自燃和爆炸,AGV电池如何进行实时监控和预测性维护?

Automated Guided Vehicle简称AGV&#xff0c;通常也称为AGV小车、自动导引运输车或无人搬运车等&#xff0c;指装备有电磁或光学等自动导引装置&#xff0c;能够沿规定的导引路径行驶&#xff0c;具有安全保护以及各种移载功能的运输车&#xff0c;通常用可充电的蓄电池来作为…

JumpServer开源堡垒机安装配置

JumpServer开源堡垒机安装与配置 一、简介二、下载与安装2.1、下载2.2、安装2.3、其他 一、简介 JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产。 支持 官网地址&#xff1a;https://www.jumpserver.org/index.html JumpServer 采用分层架构&#xff0c;…

【SNMP服务】网络杂谈(11)之什么是SNMP服务?

#2023 博客之星评选已开启–成为城市领跑者# 涉及知识点 什么是 SNMP&#xff0c;SNMP的特点&#xff0c;SNMP的服务&#xff0c;SNMP服务运行。深入了解SNMP技术。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;更多内容可去其主页关注下哈&#xff0c;不胜感…

Vue启用unity3D的exe

&#x1f430;个人主页&#xff1a;张清悠 &#x1f466;个人简介&#xff1a;张清悠&#xff0c;字澄澈&#xff0c;号寻梦客&#xff0c;爱好旅行、运动&#xff0c;主攻前端方向技术研发&#xff0c;副攻Unity 3D、C、Python人工智能等 &#x1f4dd;个人寄语&#xff1a;学…

parcel运行终端报错Uncaught ReferenceError: parcelRequire is not defined解决方案

我们通过指令 npm install parcel-bundler安装的parcel 运行起来会有一个报错 换个版本就好了 打开项目终端 我们先执行 npm uninstall parcel-bundler将错误的版本给他干掉 然后执行 npm install parcel安装正确的版本 然后运行项目 在浏览器中访问地址就一切正常了

如何预防DDOS和CC攻击

在当今数字化世界中&#xff0c;网络安全成为各行各业亟需解决的重要问题。最近&#xff0c;由于DDoS&#xff08;分布式拒绝服务&#xff09;和CC&#xff08;恶意咨询&#xff09;攻击事件的频繁发生&#xff0c;网络安全进一步引起了人们的关注。 据可靠消息源透露&#xff…

【SpringBoot】一、SpringBoot3新特性与改变详细分析

前言 本文适合具有springboot的基础的同学。 SpringBoot3改变&新特性 一、前置条件二、自动配置包位置变化1、Springboot2.X2、Springboot3.X 三、jakata api迁移1、Springboot2.X2、Springboot3.X3、SpringBoot3使用druid有问题&#xff0c;因为它引用的是旧的包 四 新特…

hdfs删除后空间不是释放,trash回收机制

一、现象 hdfs删除后&#xff0c;3天了还不删除&#xff0c;故排查排查问题 二、排查过程及原理 Trash机制&#xff0c;叫做回收站或者垃圾桶&#xff0c;默认情况下是不开启的。启用 Trash 功能后&#xff0c;从 HDFS 中删除某些内容时&#xff0c;文件或目录不会立即被清除&a…

【独家揭秘】微信可以自动加好友自动打招呼啦!

最近客户好友做某书内容爆了&#xff0c;每天都引流很多人来加他的微信&#xff0c;基本每天都需要花大量时间去通过好友&#xff0c;导致没有时间去做后续的维护发展客户&#xff0c;于是找到我大吐苦水&#xff0c;问我有什么办法可以解决&#xff1f; 嘿&#xff0c;这不巧了…

Python 基本数据类型(七)

文章目录 每日一句正能量Set&#xff08;集合&#xff09;Dictionary&#xff08;字典&#xff09;结语 每日一句正能量 生活里的累&#xff0c;一半源于生活本身&#xff0c;一半源于我们对待生活的态度。当我们感觉疲惫的时候&#xff0c;记得提醒自己保持一种幸福感&#xf…

二叉树OJ题:LeetCode--144.二叉树的前序遍历

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下LeetCode中第144道二叉树OJ题&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; 数据结构与算法专栏&#xff1a;数据结构与算法 个 人…

考完啦,回归!

暑假加训&#xff01;&#xff01;&#xff01; 不能到时候被学弟比下去了!!! 冲冲冲&#xff01;&#xff01;&#xff01;

SpringBoot整合人大金仓(kingbase8)

因客户要求使用人大金仓数据库&#xff0c;因此最近在java适配人大金仓数据库&#xff0c;在此做下笔记&#xff0c;此为第三步&#xff0c;SpringBoot整合人大金仓&#xff08;kingbase8&#xff09; 1.创建与模式对应用户&#xff0c;如我所用模式为hhh-system&#xff0c;则…

RS-FAIRFRS: COMMUNICATION EFFICIENT FAIR FEDERATED RECOMMENDER SYSTEM

RS-FAIRFRS: COMMUNICATION EFFICIENT FAIR FEDERATED RECOMMENDER SYSTEM Anonymous authors Paper under double-blind review communication efficient fair federated recommender system ICLR 2023 1. What does literature study? 结合随机抽样和确定每轮通信中要抽样…

vmware总结

1、CD/DVD设置 如果设备状态没有勾选已连接&#xff0c;那么当挂载镜像的时候&#xff0c;就会提示找不到设备。

有没有好用的ai图片处理器?这几款软件都能实现ai图片处理

朋友们&#xff0c;我今天要和你们聊一聊一个非常有趣的话题——ai图片网站&#xff01;没错&#xff0c;你没听错&#xff0c;就是那种可以让你在瞬间找到想要的图片的神奇网站。想象一下&#xff0c;你需要一张火爆的表情包&#xff0c;或者一幅令人心旷神怡的风景图&#xf…

为什么高并发这么难?非停事故频频发生?一天宕机三次?【B站、唯品会也难幸免】我们该怎么做?

宕机事件 原因解析&#xff1a; 1、一方面故障是不可避免的&#xff0c;有人为的故障&#xff08;是人就容易出错的&#xff09;和非人为的故障&#xff08;机器 Failure&#xff09;。这些是无法避免的停机&#xff0c;还有有计划的停机&#xff0c;如发布新系统、升级维护、更…

PHP 的 Logo 为什么是大象?

因为大象是世界上最好的动物。 当然&#xff0c;这只是开玩笑&#xff0c;那么为什么PHP的LOGO是大象呢&#xff1f;还有哪些关于PHP的LOGO的有趣的事情呢&#xff1f; 吉祥物-大象 ElePHPant 是一款可爱的 PHP 吉祥物&#xff0c;其设计中有一头大象。 最初的LOGO 1998 年…

golang,OpenGL,计算机图形学(一)

开发环境与依赖 github.com/go-gl/gl/v4.1-core/gl github.com/go-gl/glfw/v3.2/glfw OpenGL只提供了绘图功能&#xff0c;创建窗口是需要自己完成的。这就需要学习相应操作系统的创建窗口方法&#xff0c;比较复杂&#xff0c;并且每个操作系统都不同。为简化创建窗口的过程…