甘南州住房和城乡建设局网站什么网页游戏最火
总说
过程参考黑马程序员SpringBoot3+Vue3全套视频教程,springboot+vue企业级全栈开发从基础、实战到面试一套通关_哔哩哔哩_bilibili
目录
总说
一、Redis功能测试
1.1 添加依赖
1.2 配置
1.3 调用API测试功能
二、令牌主动失效功能
2.1 Controller层
2.2 拦截器
2.3 Controller层
2.4 测试
之前的登录逻辑,如果在修改密码后,我们会得到新的令牌,但是没有删除旧的令牌,导致用旧的令牌依然能够登录访问,所以我们在更新密码后应该要删除旧的令牌。
在用户登录成功时,我们同时向 浏览器 和 Redis 发送令牌,当浏览器携带令牌访问其他资源时,在拦截器中,我们不仅要对令牌的合法性进行校验,还要和Redis的令牌比较,二者的令牌一模一样才能通过。
我们用Redis实现旧令牌的主动失效。在用户修改密码后,我们在Redis删除令牌就能实现。
一、Redis功能测试
1.1 添加依赖
来到pom.xml中,添加Redis依赖
<!-- 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测试对象
写入代码如下:
可能会有爆红,先不管他,项目能跑
@SpringBootTest //如果在测试类上添加这个注解,在单元测试方法执行之前,会先初始化Spring容器
public class RedisTest {@Autowiredprivate StringRedisTemplate stringRedisTemplate; //这里爆红//测试存储@Testpublic 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
然后输入
get username
发现我们可以得到刚刚传入的键值对
我们再测试一下获取功能
完整代码如下:
@SpringBootTest //如果在测试类上添加这个注解,在单元测试方法执行之前,会先初始化Spring容器
public class RedisTest {@Autowiredprivate StringRedisTemplate stringRedisTemplate; //这里爆红//测试存储@Testpublic void testSet(){//往redis中存储一个键值对 StringRedisTemplate//接受一个返回值ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();//实现数据存储operations.set("username","ZhangSan");}//测试存储@Testpublic void testGet(){//往redis中获取一个键值对ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();//实现数据读取 输入到控制台System.out.println(operations.get("username"));}
}
然后测试一下:
成功得到
二、令牌主动失效功能
2.1 Controller层
来到UserController,添加对象
@Resource
private StringRedisTemplate stringRedisTemplate;
修改login方法,将token存到redis中,修改完如下:
//登录方法 ,返回值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());//生成tokenString 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 拦截器
来到拦截器
添加代码:
完整代码:
//@Component用于标识一个类是一个 Spring 管理的组件
@Component
public class LoginInterceptor implements HandlerInterceptor {@Resourceprivate StringRedisTemplate stringRedisTemplate;//preHandle方法会在请求处理之前被调用,可以在这里进行一些前置处理,比如登录验证@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//令牌验证String token = request.getHeader("Authorization");//验证tokentry {//从redis中获取相同的tokenValueOperations<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; //拦截}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//在请求处理完成之后被调用,可以在这里进行一些后置处理,比如清理ThreadLocal中的数据ThreadLocalUtil.remove();}
}
2.3 Controller层
再次回到UserController,来到updatePwd方法
在用户更新完密码后,删除redis中对应的token
在return前面添加一句:
operations.getOperations().delete(token); // 删除token
updatePwd方法完整代码如下:
//更新密码@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");// 在线程中获取用户名usernameUser 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中对应的tokenValueOperations<String, String> operations = stringRedisTemplate.opsForValue();operations.getOperations().delete(token); // 删除tokenreturn Result.success();}
2.4 测试
先启动redis和项目
来到更新用户密码接口,更新一下用户密码
修改成功之后,我们重新去登录接口
发现我们用更新后的密码可以登录成功,更新前的密码不能登录
而且在不更换token的情况下,我们不再能访问其他接口。
功能实现完成。上传git保存一下