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

news/2024/10/17 15:26:33/

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.ppmy.cn/news/653195.html

相关文章

亚马逊云科技推出Amazon AppFabric,SaaS安全不断加码

亚马逊云科技近日宣布推出Amazon AppFabric来增强公司在软件即服务&#xff08;SaaS&#xff09;应用程序方面的现有投入。Amazon AppFabric是一项无代码服务&#xff0c;可以为客户提高安全性&#xff0c;管理水平和生产力。只需在亚马逊云科技管理控制台上点击几下&#xff0…

什么是GPT?

文章目录 1、什么是GPT&#xff1f;2、gpt版本时间线3、我们能用GPT做什么&#xff1f;4、如何快速体验GPT&#xff1f;5、作为一名开发者&#xff0c;如何在代码中使用GPT&#xff1f;6、如何在现有项目中使用和部署GPT&#xff1f;7、GPT的优缺点&#xff1f;8、对于人工智能…

四六级重要单词(三)

astonishmentn. 惊奇&#xff0c;惊讶 astronautn. 宇宙航行员&#xff0c;宇航员 astronomyn. 天文学 atprep. 在…里&#xff1b;在…时 athleten. 运动员&#xff1b;田径运动员 Atlantica. 大西洋的 n. 大西洋 atmospheren. 大气&#xff1b;空气&#xff1b;气氛 …

四级语法(一)

不考纯语法题&#xff0c;但听读写译都涉及语法知识&#xff1b; &#xff08;一&#xff09;英语句子结构分类 1.简单句&#xff08;一件事&#xff09; 2.并列句&#xff08;多件事&#xff09; 3.复合句&#xff08;多件事&#xff09; &#xff08;二&#xff09;简单…

英语四级词汇:四级词组百词大关

英语四级词汇:四级词组”百词大关“ 1. at the thought of 一想到… 2. as a whole (=in general) 就整体而论 3. at will 随心所欲

四级词汇

表白链接听力视频词汇视频 P2单词的构成 词根spir呼吸、吹 inspire启发、鼓舞,in向里吹气spirspirit精神dispirit精神不振/使...批郁不欢conspire合谋、同谋、共谋expire到期(断气)perspire出汗、流汗artificial respiration人工呼吸artificial intelligence人工智能suspect疑似…

守望先锋地图英文和英雄英文

Hanamura 花村 Temple of Anubis 阿努比斯神殿 Volskaya Industries 沃斯卡娅工业区 Dorado 多拉多 Route 66 66号公路 Watchpoint: Gibraltar 观测站&#xff1a;直布罗陀 Hollywood 好莱坞 Kings Row 国王大道 Numbani 努巴尼 Ilios 伊利奥斯 Lijiang Tower 漓江塔…