总说
过程参考黑马程序员SpringBoot3+Vue3全套视频教程,springboot+vue企业级全栈开发从基础、实战到面试一套通关_哔哩哔哩_bilibili
目录
[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保存一下