实战之-Redis代替session实现用户登录

一、设计key的结构

        首先我们要思考一下利用redis来存储数据,那么到底使用哪种结构呢?由于存入的数据比较简单,我们可以考虑使用String,或者是使用哈希,如下图,如果使用String,注意他的value,用多占用一点空间,如果使用哈希,则他的value中只会存储他数据本身,如果不是特别在意内存,其实使用String就可以啦。

二、设计key的具体细节

 

所以我们可以使用String结构,就是一个简单的key,value键值对的方式,但是关于key的处理,session他是每个用户都有自己的session,但是redis的key是共享的,咱们就不能使用code了

在设计这个key的时候,我们之前讲过需要满足两点

1、key要具有唯一性

2、key要方便携带

如果我们采用phone:手机号这个的数据来存储当然是可以的,但是如果把这样的敏感数据存储到redis中并且从页面中带过来毕竟不太合适,所以我们在后台生成一个随机串token,然后让前端带来这个token就能完成我们的整体逻辑了

三、整体访问流程

        当注册完成后,用户去登录会去校验用户提交的手机号和验证码,是否一致,如果一致,则根据手机号查询用户信息,不存在则新建,最后将用户数据保存到redis,并且生成token作为redis的key,当我们校验用户是否登录时,会去携带着token进行访问,从redis中取出token对应的value,判断是否存在这个数据,如果没有则拦截,如果存在则将其保存到threadLocal中,并且放行。

四、基于Redis实现短信登录

这里具体逻辑就不分析了,之前咱们已经重点分析过这个逻辑啦。

UserServiceImpl代码

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
    // 1.校验手机号
    String phone = loginForm.getPhone();
    if (RegexUtils.isPhoneInvalid(phone)) {
        // 2.如果不符合,返回错误信息
        return Result.fail("手机号格式错误!");
    }
    // 3.从redis获取验证码并校验
    String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
    String code = loginForm.getCode();
    if (cacheCode == null || !cacheCode.equals(code)) {
        // 不一致,报错
        return Result.fail("验证码错误");
    }

    // 4.一致,根据手机号查询用户 select * from tb_user where phone = ?
    User user = query().eq("phone", phone).one();

    // 5.判断用户是否存在
    if (user == null) {
        // 6.不存在,创建新用户并保存
        user = createUserWithPhone(phone);
    }

    // 7.保存用户信息到 redis中
    // 7.1.随机生成token,作为登录令牌
    String token = UUID.randomUUID().toString(true);
    // 7.2.将User对象转为HashMap存储
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
            CopyOptions.create()
                    .setIgnoreNullValue(true)
                    .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
    // 7.3.存储
    String tokenKey = LOGIN_USER_KEY + token;
    stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
    // 7.4.设置token有效期
    stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);

    // 8.返回token
    return Result.ok(token);
}

五、解决状态登录刷新问题

5.1  初始方案思路总结:

        在这个方案中,他确实可以使用对应路径的拦截,同时刷新登录token令牌的存活时间,但是现在这个拦截器他只是拦截需要被拦截的路径,假设当前用户访问了一些不需要拦截的路径,那么这个拦截器就不会生效,所以此时令牌刷新的动作实际上就不会执行,所以这个方案他是存在问题的

LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            response.setStatus(401);
            return false;
        }
        // 2.基于TOKEN获取redis中的用户
        String key  = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            return false;
        }
        // 5.将查询到的hash数据转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6.存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7.刷新token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserHolder.removeUser();
    }
}
	

其中 UserHolder:

package com.hmdp.utils;

import com.hmdp.dto.UserDTO;

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}

5.2  优化方案

        既然之前的拦截器无法对不需要拦截的路径生效,那么我们可以添加一个拦截器,在第一个拦截器中拦截所有的路径,把第二个拦截器做的事情放入到第一个拦截器中,同时刷新令牌,因为第一个拦截器有了threadLocal的数据,所以此时第二个拦截器只需要判断拦截器中的user对象是否存在即可,完成整体刷新功能。

RefreshTokenInterceptor :

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

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

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // 2.基于TOKEN获取redis中的用户
        String key  = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5.将查询到的hash数据转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6.存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7.刷新token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserHolder.removeUser();
    }
}
	

LoginInterceptor :

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.判断是否需要拦截(ThreadLocal中是否有用户)
        if (UserHolder.getUser() == null) {
            // 没有,需要拦截,设置状态码
            response.setStatus(401);
            // 拦截
            return false;
        }
        // 有用户,则放行
        return true;
    }
}

配置拦截器:

order(num):num越大,执行时间越低

RefreshTokenInterceptor先执行,LoginInterceptor后执行

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                ).order(1);
        // token刷新的拦截器
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络技术-2022期末考试解析

【前言】 这是计算机网络技术这门课&#xff0c;感觉和计网还是有不一样的&#xff0c;但也有能做的&#xff0c;把能做的做了。 一、单项选择题&#xff08;每题2分&#xff0c;共20分&#xff09; 1. 用于测试两台计算机连通状况的命令是 。 ( ) A. cmd B. ping C. ipconf…

代码随想录day30 回溯算法最终章

51. N皇后 题目 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案。 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案&#xff0c;该方案中 Q 和…

TIFF转JPG助手:轻松批量转换,优化图片管理

在数字时代&#xff0c;图片已成为我们生活和工作中不可或缺的一部分。为了更好地管理和使用这些图片&#xff0c;我们需要一个强大的工具来帮助我们转换和优化图片格式。TIFF转JPG助手正是这样一款理想的解决方案 首先&#xff0c;我们进入首助编辑高手主页面&#xff0c;会看…

嵌入式软件面试之程序在存储器中的分布

Hi, 大家好&#xff0c;今天阿目分享的是一个嵌入式软件面试的常见问题&#xff0c;内存分布或者说程序在内存中的布局&#xff0c;我们写的程序是按照怎么的准则放在内存中的&#xff1f; 一般有操作系统的嵌入式设备&#xff0c;都会有一个Bootloader, 它负责在上电后初始化…

70.网游逆向分析与插件开发-角色数据的获取-自动化助手UI显示角色数据

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;利用技能点属性分析角色数据基址-CSDN博客 码云地址&#xff08;ui显示角色数据 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;367aa71f60b…

HCIP之OSPF大实验

华子目录 实验拓扑及要求实验步骤合理划分网段配置IP地址两个area 0区域可以访问公网首先使用ospf使私网通在边界路由器上写静态缺省在边界路由器上做nat&#xff0c;实现公网访问在边界路由器上强制下放缺省 使用GRE连接两个area 0&#xff0c;解决不规则区域划分在边界路由器…

【Vue3】2-11 : 生命周期钩子函数及原理分析

本书目录&#xff1a;点击进入 一、组件生命周期概述 1.1 官方生命周期 1.2 钩子函数&#xff08;回调函数&#xff09; ▶ 生命周期可划分为三个部分(- >表示执行循序)&#xff1a; 二、实战&#xff1a;测试生命周期流程 &#xff1e; 代码 &#xff1e; 效果 一…

(十二)EEPROM的补充

文章目录 EEPROM补充篇读EEPROM补充内容写EEPROM补充内容单字节写入多字节拆成单字节写入现象 EEPROM补充篇 读EEPROM补充内容 对于上一篇博文在读EEPROM的时候&#xff0c;提到的DUMMY WRITE&#xff1a; 这里怎么理解呢&#xff1a; 大家看&#xff0c;写EEPROM的逻辑除了…

MySQl Mybatis

一、MySQL 1.1 概述 1.1.1 MySQL安装 1.1.2 数据模型 1.1.3 SQL简介 1.2 DDL 1.2.1 数据库操作 1.2.2 图形化工具 1.2.3 表结构操作 &#xff08;一&#xff09;创建 &#xff08;二&#xff09;数据类型 &#xff08;1&#xff09;数值类型 age tinyint unsigned——加上…

技术阅读周刊第十四期:Golang 作者 Rob Pike 在 GopherConAU 上的分享

技术阅读周刊&#xff0c;每周更新。 历史更新 20231215&#xff1a;第十期20231122&#xff1a;第十一期20231129&#xff1a;第十二期20240105&#xff1a;第十三期&#xff1a;一些提高生产力的终端命令 What We Got Right, What We Got Wrong URL: https://commandcenter.b…

pinyin-pro库使用方式

pinyin-pro 是一个专业的 JavaScript 中文转拼音的库&#xff0c;具备多音字识别准确、体积轻量、性能优异、功能丰富等特点。 pinyin-pro官网链接&#xff1a;介绍 | pinyin-pro 运行展示 pinyin-pro安装命令&#xff1a; # 选择一个你使用的包管理器进行安装即可# NPM $ n…

基于Java SSM框架实现企业车辆管理系统项目【项目源码】

基于java的SSM框架实现企业车辆管理系统演示 JSP技术 JSP技术本身是一种脚本语言&#xff0c;但它的功能是十分强大的&#xff0c;因为它可以使用所有的JAVA类。当它与JavaBeans 类进行结合时&#xff0c;它可以使显示逻辑和内容分开&#xff0c;这就极大的方便了运动员的需求…

为什么使用双token实现无感刷新用户认证?

单token机制 认证机制&#xff1a;对与单token的认证机制在我们项目中仅使用一个Access Token的访问令牌进行用户身份认证和授权的方案处理。 不足之处&#xff1a; 安全性较低&#xff08;因为只有一个token在客户端和服务器端之间进行传递&#xff0c;一旦Access Token被截…

对闭包的理解

概念&#xff1a; 一个函数对周围状态的引用捆绑在一起&#xff0c;闭包让开发者可以从内部函数访问外部 函数的作用域 简单理解&#xff1a;闭包 内层函数 外层函数的变量 一个函数对周围状态的引用捆绑在一起&#xff0c;闭包让开发者可以从内部函数访问外部 函数的作…

【5G Modem】5G modem架构介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

openFeign 多模块调用失败问题

第一次做一个完整的SpringCloud微服务项目,踩了好多好多坑,都记录下来! openFeign 多模块调用失败 排错第一阶段 创建一个openfeign服务,并把它注册到nacos上去 然后A模块通过Feign调用B模块 但是我在A模块实现AdminArticleServiceFeignClient这个接口,报错: 后面我查找这个问…

phpinfo和php -m 加载的php.ini不一致

目的&#xff1a; 将phpinfo在web中展示的php.ini和在命令行中展示的php.ini加载路径设置一致。 原本的php.ini加载路劲是&#xff1a; /usr/local/lib/php.ini 解决思路&#xff1a; &#xff08;1&#xff09;which php 查看服务器加载的php的位置&#xff0c;这里原来是&a…

win10 安装配置 Rust 环境和简单使用

文章目录 安装 Rustup基本命令hello wrold使用 cargo 创建项目构建并运行项目发布 最近几年&#xff0c;Rust 因其卓越的内存安全性和并发性能备受关注。不仅连续七年获得 StackOverflow 最受开发者喜爱的语言榜榜首&#xff0c;也在越来越多知名公司内部使用&#xff0c;比如&…

通过Wireshark抓包分析谈谈DNS域名解析的那些事儿

原创/朱季谦 本文主要想通过动手实际分析一下是如何通过DNS服务器来解析域名获取对应IP地址的&#xff0c;毕竟&#xff0c;纸上得来终觉浅&#xff0c;绝知此事要躬行。 一、域名与IP地址 当在浏览器上敲下“www.baidu.com”时&#xff0c;一键回车&#xff0c;很快&#x…

分布式链路追踪专栏,Spring Cloud Sleuth:分布式链路追踪之通信模型设计

Spring Cloud Sleuth 赋予分布式跟踪的 Spring Boot 自动配置的一键解决方案。Spring Cloud Sleuth 是基于 Brave 的封装&#xff0c;也是很多公司采用开源加自研的最佳解决方案。 那么从作为架构师或者技术专家如何去借鉴优秀框架的设计理念和思想&#xff0c;本次 Chat 将开…