登录优化:
背景:
如果使用密码进行登录时,会生成一个jwt令牌,如果我们进行修改密码操作,会得到一个新的jwt令牌,但是原来的旧jwt令牌任然会生效。
令牌主动失效机制:
我们通过登录生成的jwt令牌,给redis中也存储一份,我们进行其它操作时,在拦截器这里既要对浏览器携带的令牌进行合法性校验,也要从redis获取一份一模一样的令牌(获取不到的话证明令牌已经失效),都满足的话就正常提供服务。当用户修改密码成功后,就把redis中存储的jwt令牌删除掉)。
1、登录成功后,给浏览器响应令牌的同时,把该令牌存储到redis中
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
@Autowired
private UserService userService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@PostMapping("/login")
public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username,@Pattern(regexp = "^\\S{5,16}$") String password){
//根据用户名查询User
User loginUser = userService.findByUserName(username);
//判断是否查到
if(loginUser == null){
return Result.error("用户名错误");
}
//判断密码是否正确
if(Md5Util.getMD5String(password).equals(loginUser.getPassword())){
//登录成功
Map<String,Object> claims = new HashMap<>();
claims.put("id",loginUser.getId());
claims.put("username",loginUser.getUsername());
String token = JwtUtil.generateJwt(claims);
//把token存储到redis之中
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.set(token,token,43200000L, TimeUnit.MILLISECONDS);//token既是键又是值、时间、时间单位
return Result.success(token);
}
return Result.error("密码错误");
}
}
2、LoginInterceptor拦截器中,需要验证浏览器(请求头为Authorization)携带的令牌,并同时需要获取到redis中存储的与之相同的令牌
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//令牌验证
String token = request.getHeader("Authorization"); //请求头的名称
//验证token
try {
//从redis中获取tokne
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
String redisToken = operations.get(token);
if(redisToken == null) /* token已经失效 */ throw new RuntimeException();
Claims claims = JwtUtil.parseJWT(token);
//把业务数据存储到Threadlocal中
ThreadLocalUtil.set(claims);
return true;
} catch (Exception e) {
//http响应状态码为401
response.setStatus(401);
//不放行
return false;
}
}
}
3、当用户修改密码成功后,删除redis中存储的旧令牌
(获取原来的token我们通过在参数列表上添加一个参数并加上@requestheader注解,就会把请求头的值自动赋值给参数)
//更新密码
@PatchMapping("/updatePwd")
//springmvc框架会自动把json数据转换成map集合对象
public Result updatePwd(@RequestBody Map<String,String> params,@RequestHeader("Authorization") String token){
//校验参数
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("缺少必要的参数");
}
//原密码是否正确、根据用户名拿到原密码
Map map = ThreadLocalUtil.get();
String user = (String) map.get("username");
User loginUser = userService.findByUserName(user);
//oldpwd
if(!loginUser.getPassword().equals(Md5Util.getMD5String(oldPwd))){
return Result.error("原密码填写不正确");
}
//newpwd、repwd
if(!rePwd.equals(newPwd)){
return Result.error("两次填写的新密码不一样");
}
//调用service完成密码更新
userService.updatePwd(newPwd);
//删除原来redis对应的token
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.getOperations().delete(token);
return Result.success();
}
如何在springboot中集成redis
1、导入spring-boot-start-data-redis起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、在yml配置文件中,配置redis连接信息
spring:
data:
redis:
host: localhost
port: 6379
# password: ******
3、调用API(StringRedisTemplate)完成字符串的存取操作
测试实例: