点评项目-4-隐藏敏感信息、使用 redis 优化登录业务

news2024/11/27 3:48:19

一、隐藏敏感信息

之前我们对 /user/me 路径,直接返回了登录的所有用户信息,其中的 passward 等敏感信息也会被返回到前端,这是很危险的,故我们需要选择性的返回用户信息,隐藏敏感用户信息

我们可以创建一个 UserDTO 类将 user 中可以返回的信息封装到其中后,将 UserDTO 返回

@Data
public class UserDTO {
    private Long id;
    private String nickName;
    private String icon;
}

然后我们将登录成功时,存入 user 的操作,改为存入 UserDTO ,这里使用的是 hutool 工具中 BeanUtil 来封装,将之前的 user 换成 UserDTO 后再存入 session 

        //保存用户登录信息
//        session.setAttribute("user",user);
        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

在拦截器进行登录校验时,我们会拿出这个 user ,需要将拦截器的对应代码也修改

    //前置拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //拿到 session 中的 user
        Object user = request.getSession().getAttribute("user");
        System.out.println(user+"进入前置拦截器");
        //若用户不存在,拦截
        if(user == null){
            response.setStatus(401);//响应 401 状态码,表示未授权
            return false;
        }
        //将用户保存在 ThreadLocal 中,调用 UserHolder 中的静态方法
        UserHolder.saveUser((UserDTO) user);
        //放行
        System.out.println("前置拦截放行");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
public class UserHolder {

    //user 对应的的线程池
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    //往线程池中存入用户
    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    //拿到当前线程的 user,一个线程只有一个 user
    public static UserDTO getUser(){
        return tl.get();
    }

    //移除当前线程的 user
    public static void removeUser(){
        tl.remove();
    }

}

修改完毕后,我们再次使用 postman 进行测试,完成发验证码,登录,等了校验三个请求

可以看到,这次返回的用户信息只含有 id,昵称,头像。保护了敏感信息。

二、解决集群的 session 共享问题

使用 session 进行登录信息的存储,当出现多台 tomcat 时,存储的信息会出现无法共享的情况,我们可以通过 Redis 来存储信息来解决

对于验证码,我们可以使用手机号作为 key 验证码存入 value中;

对于登录信息,我们可以使用哈希结构存储不同的信息,使用随机 token 生成随机且唯一的 key,在响应时,将 token 返回给浏览器,在之后需要用到 token 的请求,需要在请求中发送 token

在完成业务之前,我们先配置一下 redis 并测试是否可以正常访问

yml 配置文件:

server:
  port: 8082

spring:
  application:
    name: mydp
  datasource:
    url: jdbc:mysql://localhost:3306/learnbase
    username: root
    password: 1234
  redis:
    host: 127.0.0.1
    port: 6379
    lettuce:
      pool:
        max-active: 8 # 最大连接
        max-idle: 8 # 最大空闲连接
        min-idle: 0
        max-wait: 100 # 最大等待时间,单位毫秒
    jackson:
      default-property-inclusion: non_null # JSON处理时忽略非空字段

测试 redis 是否连接正常

@SpringBootTest
class MyDianpingApplicationTests {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void redisText() {
        stringRedisTemplate.opsForValue().set("dataRides","check");
        Object dataRides = stringRedisTemplate.opsForValue().get("dataRides");
        System.out.println(dataRides);
    }

}

若 redis 中写入了键值对 dataRides , check 则可以认为连接时畅通的

接下来我们便可以通过 Redis 来优化登录业务了

发送验证码

首先在成员变量位置注入 StringRedisTemplate

    @Resource
    private StringRedisTemplate stringRedisTemplate;

Controller 层和 UserService 接口无需改动,只需修改 UserServiceImpl 的 sendCode 方法即可

    @Override
    public Result sendCode(String phone, HttpSession session) {
        //先用 hutool 工具校验手机号是否合法
        if (phone == null || !PhoneUtil.isPhone(phone)) {
            return Result.fail("请输入合法的手机号");//若不合法直接响应错误
        }
        //用 hutool 工具生成六位验证码
        String code = RandomUtil.randomNumbers(6);
        //将生成的验证码放入 session
        //TODO 优化:保存验证码到redis
        //TODO 后面两个参数时验证码的有效期,2分钟,设置后 redis 会在底层加上 set key value ex 120
        stringRedisTemplate.opsForValue().set("login:code:"+phone,code,2, TimeUnit.MINUTES);
        //给手机号发送验证码,这里模拟发送验证码的操作,而不是真正的发送
        System.out.println("手机收到了一条验证码短信:"+code);
        return Result.ok();
    }

登录逻辑

从 redis 中获取验证码并验证,验证通过后存入用户信息到 redis

生成 token 作为唯一的标识符,将用户信息封装为哈希表,将其挂在 token 下后存入 redis

最后返回 token ,在之后每次需要用到用户信息的请求中,我们都将 token 作为请求头发送请求

    @Override
    public Result login(LoginFormDTO loginFormDTO, HttpSession session) {
        if(loginFormDTO == null){
            return Result.fail("无效操作");
        }
        //校验手机号
        String phone = loginFormDTO.getPhone();
        if (phone == null || !PhoneUtil.isPhone(phone)) {
            return Result.fail("请输入合法的手机号");//若不合法直接响应错误
        }
        //拿到 session 域中的验证码
        //TODO 优化:从 redis 中获取验证码
        String code1 = stringRedisTemplate.opsForValue().get("login:code:"+phone);
        String code = loginFormDTO.getCode();
        if(!code.equals(code1)){
           return Result.fail("验证码输入错误,请重新输入");
        }
        //验证码正确,判断是否存在用户
        User user = userMapper.selectByPhone(phone);
        if(user == null){
            //若不存在就创建一个用户并存入 mysql
            user = createUserByPhone(phone);
            userMapper.insert(user);
        }
        //保存用户登录信息
        //TODO 优化:生成 token ,使用哈希的方式保存用户信息
        //使用 hutool 的 UUID 来生成 token
        String token = UUID.randomUUID().toString();
        UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);
        //使用 stringRedisTemplate,使用 hash 的方式存储存信息时必须保证所有 key value 都是 String 类型
        Map<String,String> userMap = new HashMap<>();
        userMap.put("id",Long.toString(userDTO.getId()));
        userMap.put("nickName",userDTO.getNickName());
        userMap.put("icon",userDTO.getIcon());
        stringRedisTemplate.opsForHash().putAll("login:token:"+token,userMap);
        //设置 token 有效日期,此处设定初始有效日期,可以通过通用拦截器更新有效日期
        stringRedisTemplate.expire("login:token:"+token,30,TimeUnit.MINUTES);
        //TODO 优化:返回生成的 token
        return Result.ok(token);
    }

 拦截器更新 token 有效期,登录验证

为了做到当用户完成任何操作后,token 的有效期更新,以保证用户的使用体验,我们可以设置一个全局拦截器,将 token 的更新操作写在全局拦截器中,并在全局拦截器中将用户信息存入线程池中

再使用第二级拦截器判断用户是否登录,登录的用户一定会存在于线程池,我们可以通过此来判断用户是否登录

一级拦截
public class RefreshTokenInterceptor implements HandlerInterceptor {

    //这个类没有被 Spring 管理,我们可以使用其配置类 MvcConfig 拿到后通过有参构造传递进来
    private StringRedisTemplate stringRedisTemplate;
    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }

    //前置拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //获取请求头中的 token
        String token = request.getHeader("authorization");
        if(StrUtil.isBlank(token)){
            //token 不存在,直接放行
            return true;
        }

        //基于 token 取用户信息
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries("login:token:"+token);
        if(userMap.isEmpty()){
            //没有信息,直接放行
            return true;
        }
        //有用户信息,将 userMap 转为 UserDTO 后保存到 ThreadLocal 中
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//最后一个参数表示是否忽略转换过程中的错误
        UserHolder.saveUser(userDTO);
        //更新 token 的失效时间
        stringRedisTemplate.expire("login:token:"+token,30, TimeUnit.MINUTES);
        //放行
        return true;
    }

    //渲染后拦截
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //在渲染后将 user 从线程中移除
        UserHolder.removeUser();
    }
}
二级拦截
public class LoginInterceptor implements HandlerInterceptor {

    //前置拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断线程池中是否有登录用户,若没有则拦截,返回 401 状态码
        if(UserHolder.getUser() == null){
            response.setStatus(401);
            return false;
        }
        return true;
    }

}

测试

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

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

相关文章

ECCV`24 | 新加坡国立华为提出Vista3D: 实现快速且多视角一致的3D生成

文章链接&#xff1a;https://arxiv.org/pdf/2409.12193 gitbub链接&#xff1a;https://github.com/florinshen/Vista3D 亮点直击 提出了Vista3D&#xff0c;一个用于揭示单张图像3D darkside 的框架&#xff0c;能够高效地利用2D先验生成多样的3D物体。开发了一种从高斯投影到…

43 C 程序动态内存分配:内存区域划分、void 指针、内存分配相关函数(malloc、calloc、realloc、_msize、free)、内存泄漏

目录 1 C 程序内存区域划分 1.1 代码区 (Code Section) 1.2 全局/静态区 (Global/Static Section) 1.3 栈区 (Stack Section) 1.4 堆区 (Heap Section) 1.5 动态内存分配 2 void 指针&#xff08;无类型指针&#xff09; 2.1 void 指针介绍 2.2 void 指针的作用 2.3 …

Web自动化Demo-Go+Selenium

1.新建工程 使用GoLand新建工程如下&#xff1a; 打开终端输入如下命令安装Selenium go get -u github.com/tebeka/selenium 2.编写代码 package mainimport ("fmt""github.com/tebeka/selenium""log""time" )const (chromeDriver…

视频生成的黎明:100+页干货全面探讨SORA类模型(T2VI2VV2V全包括)腾讯中科大

日前&#xff0c;腾讯AI Lab和中科大联合发布了100多页的类SORA模型研究报告&#xff0c;非常全面&#xff0c;很有学习和研究价值&#xff0c;今天和大家分享下&#xff0c;内容较多&#xff0c;可后台回复【类SORA报告】获取100多页pdf。 开源地址&#xff1a;https://ailab-…

函数信号发生器的直流偏置(OFFSET)旋钮的作用及操作方法

函数信号发生器&#xff08;Function Generator&#xff09;是电子工程师和技术人员在电路设计、测试和调试中常用的设备之一。它可以生成各种标准波形&#xff0c;如正弦波、方波、三角波等&#xff0c;以及用户自定义的任意波形。在众多参数设置中&#xff0c;直流偏置&#…

2025考研今天开始预报名!攻略请查收

2025年全国硕士研究生招生考试 今天起开始预报名 有什么流程&#xff1f;需要准备哪些信息&#xff1f; 这份考研报名攻略速查收 ↓↓↓ 全国硕士研究生招生考试报名包括网上报名和网上确认两个阶段&#xff1a; 网上预报名时间为10月9日至10月12日&#xff08;每日9&#xff1…

电脑屏保设置教程 好看的电脑屏保应该怎么设置?

一、电脑自带的屏保设置&#xff0c;主题少&#xff0c;操作复杂&#xff1b; 你需要选择一个合适的屏保。在Windows系统中&#xff0c;你可以通过以下步骤找到合适的屏保&#xff1a; 右键点击桌面空白处&#xff0c;选择“个性化”&#xff1b; 在“个性化”设置中&#x…

win10家庭版配置ubantu20.04子系统

需要在本地配置一个环境去跑代码&#xff0c;代码的环境如下&#xff1a; 刚开始准备给电脑装一个双系统的&#xff0c;室友踩过坑告诉我安装wsl子系统就可以了&#xff0c;方便快捷~ 于是开始了摸索之旅&#xff01;记录如下 &#xff08;我刚开始以为一定要win10专业版&…

人才画像的重要性,如何打造精准人才画像?

人才画像在人力资源管理中占据重要地位&#xff0c;尤其是在人才招聘环节&#xff0c;它发挥着不可替代的作用&#xff0c;制定精准的人才画像有助于优化招聘和人力资源管理&#xff0c;从而提高组织竞争力和发展潜力。 一、人才画像的重要性 提高招聘精准度&#xff1a;精准…

考试系统之题目反馈

在现代教育体系中&#xff0c;考试不仅是检验学生学习成果的重要手段&#xff0c;也是教师评估教学效果、调整教学策略的关键环节。随着科技的飞速发展&#xff0c;传统的纸质考试逐渐被在线考试系统所取代&#xff0c;而题目反馈功能作为在线考试系统不可或缺的一部分&#xf…

电磁兼容(EMC):整改案例(五)EFT测试,改初级Y电容

目录 1. 异常现象 2. 原因分析 3. 整改方案 4. 总结 1. 异常现象 某产品按GB/T 17626.4标准进行电快速瞬变脉冲群测试&#xff0c;测试条件为&#xff1a;频率5kHz/100kHz&#xff0c;测试电压L&#xff0c;N线间2kV。其中频率5kHz时&#xff0c;测试通过&#xff0c;但频…

开源 AI 智能名片 O2O 商城小程序源码助力企业实现三层式个性化体验

摘要&#xff1a;本文探讨了在数字化时代&#xff0c;企业如何利用开源 AI 智能名片 O2O 商城小程序源码实现三层式个性化体验。通过分析数字化空间的定制化和个性化服务特点&#xff0c;以及实体经营中对人际互动的依赖&#xff0c;阐述了随着物联网和人工智能基础设施的开发&…

[Python] 使用Python自定义生成二维码

文章目录 目录 安装 qrcode 库生成简单的二维码代码讲解 生成自定义样式的二维码代码讲解 生成带有链接的二维码代码讲解 Demo代码实现代码讲解 总结 收录专栏: [Python] 二维码是现在非常常用的一种信息存储和传递方式&#xff0c;我们可以通过扫描二维码来快速获取文本、链接…

史上最全JLInk调试Kevil指南||一篇就够了||从菜鸟到调试大佬

目录 写在前面 问题1&#xff1a;jlink的port选项有SW和JTAG&#xff0c;这两个有什么区别&#xff0c;为什么大多数情况下选SW&#xff1f; 1.配置reset and run,下載即可重启 2.寄存器 问题2:keil中debug调试中会出现register和value&#xff0c;register中包括R0&#…

QT入门教程攻略 QT入门游戏设计:贪吃蛇实现 QT全攻略心得总结

Qt游戏设计&#xff1a;贪吃蛇 游戏简介 贪吃蛇是一款经典的休闲益智类游戏&#xff0c;玩家通过控制蛇的移动来吃掉地图上的食物&#xff0c;使蛇的身体变长。随着游戏的进行&#xff0c;蛇的移动速度会逐渐加快&#xff0c;难度也随之增加。当蛇撞到墙壁或自己的身体时&…

LLM详解

一 定义 Large Language Model&#xff0c;称大规模语言模型或者大型语言模型&#xff0c;是一种基于大量数据训练的统计语言模型&#xff0c;可用于生成和翻译文本和其他内容&#xff0c;以及执行其他自然语言处理任务&#xff08;NLP&#xff09;&#xff0c;通常基于深度神…

verilog端口使用注意事项

下图存在组合逻辑反馈环&#xff0c;即组合逻辑的输出反馈到输入(赋值的左右2边存在相同的信号)&#xff0c;此种情况会造成系统不稳定。比如在data_in20的情况下&#xff0c;在data_out0 时候&#xff0c;输出的数据会反馈到输入&#xff0c;输入再输出&#xff0c;从而造成不…

深度学习常见问题

1.YOLOV5和YOLOV8的区别 YOLOv5 和 YOLOv8 是两个版本的 YOLO&#xff08;You Only Look Once&#xff09;目标检测算法&#xff0c;它们在网络架构、性能优化、功能扩展等方面有显著的区别。YOLOv5 是 YOLO 系列的重要改进版本&#xff0c;而 YOLOv8 是最新的一次重大升级&am…

【C++网络编程】(一)Linux平台下TCP客户/服务端程序

文章目录 Linux平台下TCP客户/服务端程序服务端客户端相关头文件介绍 Linux平台下TCP客户/服务端程序 图片来源&#xff1a;https://subingwen.cn/linux/socket/ 下面实现一个Linux平台下TCP客户/服务端程序&#xff1a;客户端向服务器发送&#xff1a;“你好&#xff0c;服务…

大数据-159 Apache Kylin 构建Cube 准备和测试数据

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…