Redis实现登录优化

总说

过程参考黑马程序员SpringBoot3+Vue3全套视频教程,springboot+vue企业级全栈开发从基础、实战到面试一套通关_哔哩哔哩_bilibili

目录

总说

一、Redis功能测试

[1.1 添加依赖](#1.1 添加依赖)

[1.2 配置](#1.2 配置)

[1.3 调用API测试功能](#1.3 调用API测试功能)

二、令牌主动失效功能

[2.1 Controller层](#2.1 Controller层)

[2.2 拦截器](#2.2 拦截器)

[2.3 Controller层](#2.3 Controller层)

[2.4 测试](#2.4 测试)


之前的登录逻辑,如果在修改密码后,我们会得到新的令牌,但是没有删除旧的令牌,导致用旧的令牌依然能够登录访问,所以我们在更新密码后应该要删除旧的令牌

在用户登录成功时,我们同时向 浏览器 和 Redis 发送令牌,当浏览器携带令牌访问其他资源时,在拦截器中,我们不仅要对令牌的合法性进行校验,还要和Redis的令牌比较,二者的令牌一模一样才能通过。

我们用Redis实现旧令牌的主动失效。在用户修改密码后,我们在Redis删除令牌就能实现。

一、Redis功能测试

1.1 添加依赖

来到pom.xml中,添加Redis依赖

XML 复制代码
<!-- redis依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>3.4.2</version>
</dependency>

1.2 配置

来到application.yml中,添加配置如下:

1.3 调用API测试功能

在test目录下创建一个RedisTest测试对象

写入代码如下:

可能会有爆红,先不管他,项目能跑

java 复制代码
@SpringBootTest //如果在测试类上添加这个注解,在单元测试方法执行之前,会先初始化Spring容器
public class RedisTest {

    @Autowired
    private StringRedisTemplate stringRedisTemplate; //这里爆红

    //测试存储
    @Test
    public void testSet(){
        //往redis中存储一个键值对 StringRedisTemplate
        //接受一个返回值
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();

        //实现数据存储
        operations.set("username","ZhangSan");
    }
}

没有redis,要先去安装redis

下完redis后,点击redis-server.exe启动redis

然后我们去启动测试类:

左边绿色,表示运行成功

然后我们再去redis,看看我们的键值对是否成功存入

打开redis-cil.exe

然后输入

java 复制代码
get username

发现我们可以得到刚刚传入的键值对

我们再测试一下获取功能

完整代码如下:

java 复制代码
@SpringBootTest //如果在测试类上添加这个注解,在单元测试方法执行之前,会先初始化Spring容器
public class RedisTest {

    @Autowired
    private StringRedisTemplate stringRedisTemplate; //这里爆红

    //测试存储
    @Test
    public void testSet(){
        //往redis中存储一个键值对 StringRedisTemplate
        //接受一个返回值
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();

        //实现数据存储
        operations.set("username","ZhangSan");
    }

    //测试存储
    @Test
    public void testGet(){
        //往redis中获取一个键值对
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();

        //实现数据读取 输入到控制台
        System.out.println(operations.get("username"));
    }
}

然后测试一下:

成功得到

二、令牌主动失效功能

2.1 Controller层

来到UserController,添加对象

java 复制代码
@Resource
private StringRedisTemplate stringRedisTemplate;

修改login方法,将token存到redis中,修改完如下:

java 复制代码
//登录方法 ,返回值token是一个字符串
    @PostMapping("/login")
    public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$")String password) {
        //1、根据用户名查询用户
        User user = userService.findByUserName(username);//返回一个查找到的User对象
        //2、判断用户是否存在
        if (user == null) { //没找到
            return Result.error("用户名不存在");
        }
        //3、判断密码是否正确 user对象中是加密过的密码
        if (Md5Util.getMD5String(password).equals(user.getPassword())) {
            //密码正确 登录成功
            //创建一个map,用于记录传入token的数据集合
            Map<String, Object> claims = new HashMap<>();
            claims.put("id", user.getId()); //传入id和用户名
            claims.put("username", user.getUsername());
            //生成token
            String token = JwtUtil.genToken(claims); //传入数据 并生成token

            //把token存储到redis中
            ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
            operations.set(token, token, 1, TimeUnit.HOURS);// 过期时间与token过期时间一致
            return Result.success(token);
        }
        return Result.error("密码错误");
    }

2.2 拦截器

来到拦截器

添加代码:

完整代码:

java 复制代码
//@Component用于标识一个类是一个 Spring 管理的组件
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //preHandle方法会在请求处理之前被调用,可以在这里进行一些前置处理,比如登录验证
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //令牌验证
        String token = request.getHeader("Authorization");
        //验证token
        try {

            //从redis中获取相同的token
            ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
            String redisToken = operations.get(token);

            if(redisToken == null) {
                //token已经失效
                throw new Exception(); // 抛出异常
            }

            Map<String, Object> claims = JwtUtil.parseToken(token); //解析token 解析后的内容是一个map

            //可以把业务数据存储到ThreadLocal中
            ThreadLocalUtil.set(claims);

            return true; //如果解析成功,代表登录过了,放行
        } catch (Exception e) { //如果解析失败,代表没有登录,拦截
            response.setStatus(401);
            return false; //拦截
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //在请求处理完成之后被调用,可以在这里进行一些后置处理,比如清理ThreadLocal中的数据
        ThreadLocalUtil.remove();
    }
}

2.3 Controller层

再次回到UserController,来到updatePwd方法

在用户更新完密码后,删除redis中对应的token

在return前面添加一句:

java 复制代码
operations.getOperations().delete(token); // 删除token

updatePwd方法完整代码如下:

java 复制代码
//更新密码
    @PatchMapping("/updatePwd")
    public Result updatePwd(@RequestBody Map<String, String> params,@RequestHeader("Authorization") String token){
        //1、校验参数
        String oldPwd = params.get("old_pwd");
        String newPwd = params.get("new_pwd");
        String rePwd = params.get("re_pwd");

        if (!StringUtils.hasLength(oldPwd) || !StringUtils.hasLength(newPwd) || !StringUtils.hasLength(rePwd)) {
            return Result.error("缺少必要参数");
        }

        //2、检验密码是否正确
        Map<String, Object> map = ThreadLocalUtil.get();
        String username = (String) map.get("username");// 在线程中获取用户名username

        User user = userService.findByUserName(username); //根据用户名查询用户
        if(!user.getPassword().equals(Md5Util.getMD5String(oldPwd))) {
            return Result.error("原密码不正确");
        }

        //2次填写密码是否一致
        if(!rePwd.equals(newPwd)) {
            return Result.error("两次密码不一致");
        }

        //3、调用service完成密码更新
        userService.updatePwd(newPwd);

        //删除redis中对应的token
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        operations.getOperations().delete(token); // 删除token

        return Result.success();
    }

2.4 测试

先启动redis和项目

来到更新用户密码接口,更新一下用户密码

修改成功之后,我们重新去登录接口

发现我们用更新后的密码可以登录成功,更新前的密码不能登录

而且在不更换token的情况下,我们不再能访问其他接口。

功能实现完成。上传git保存一下

相关推荐
SuperherRo43 分钟前
JAVA攻防-Shiro专题&断点调试&有key利用链&URL&CC&CB&原生反序列化&加密逻辑
java·shiro·反序列化·有key·利用链·原生反序列化·加密逻辑
QQ_43766431444 分钟前
Redis协议与异步方式
数据库·redis·bootstrap
桦说编程1 小时前
简单方法实现子任务耗时统计
java·后端·监控
爱笑的眼睛111 小时前
超越可视化:降维算法组件的深度解析与工程实践
java·人工智能·python·ai
BD_Marathon1 小时前
配置文件分类
spring boot
M***Z2101 小时前
springboot中配置logback-spring.xml
spring boot·spring·logback
盖世英雄酱581361 小时前
物品超领取损失1万事故复盘(一)
java·后端
CryptoRzz1 小时前
印度尼西亚(IDX)股票数据对接开发
java·后端·websocket·web3·区块链
f***28142 小时前
Springboot中使用Elasticsearch(部署+使用+讲解 最完整)
spring boot·elasticsearch·jenkins