大家好,今天我又又又来了,hhhhh。
上文中 我们永redis缓存了token 但是我们发现了 一个bug ,redis中缓存的token 是单用户才能实现的。
就是 我 redis中存储的键 名 为token 值 是jwt令牌 ,但是如果 用户a 登录 之后 创建一个 键 为token的 键值对,如果用户b登录,创建的的键名也是 token ,这样用户b的 jwt 会覆盖 用户a的,就会导致 用户a 的token 会失效呀,这真是一个大大的bug , 按照我目前的水平,我想到了一下 的解决方案 ,既然 token 的键会覆盖,那么我们给 token的键 加上一个唯一标识不就好了
解决前的代码
package com.example.getway.globalfilter;
import cn.hutool.core.collection.CollUtil;
import com.example.getway.Untils.JwtUntils;
import com.example.getway.commen.MessageConstant;
import com.example.getway.pojo.JwtProperties;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;
@RequiredArgsConstructor
@Slf4j
@Component
public class TokenGlobalFilter implements GlobalFilter, Ordered {
private final JwtProperties jwtProperties;
private final RedisTemplate redisTemplate;
// 这个过滤器 请求的转发
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//我们只要是非登录请求全都要检验jwt 然后进行 用户信息的传递
//获取request对象
ServerHttpRequest request = exchange.getRequest();
RequestPath requestPath = request.getPath();
if(requestPath.toString().contains("/userLogin")){
return chain.filter(exchange);
}
//获取请求头的 token
// String redisToken =null;
// List<String> authorization = request.getHeaders().get("authorization");
// if (!CollUtil.isEmpty(authorization)) {
// redisToken = authorization.get(0);
// }
String redisToken = (String)redisTemplate.opsForValue().get(MessageConstant.TOKEN);
log.info("token:{}",redisToken);
if (redisToken != null ) {
//进行jwt的解析
try {
Claims claims = JwtUntils.parseJwt(redisToken, jwtProperties.getSecretkey());
//每次 访问其他资源的时候 都把token更新
redisTemplate.expire(MessageConstant.TOKEN, 1000, TimeUnit.DAYS);
String loginId = claims.get(MessageConstant.LOGIN_ID).toString();
log.info("网关层当前用户的id:{}", Long.valueOf(loginId));
//证明 token有效 传递用户信息
ServerWebExchange loginId1 = exchange.mutate()
.request(b -> b.header("loginId", loginId))
.build();
return chain.filter(loginId1);
} catch (Exception e) {
log.info("{}",e.getMessage());
//出现异常返回一个异常响应
ServerHttpResponse response = exchange.getResponse();
response.setRawStatusCode(401);
return response.setComplete();
}
}
log.info("token错误");
return exchange.getResponse().setComplete();
}
//过滤器链中的优先级 数值越低 优先级就越高
@Override
public int getOrder() {
return 0;
}
}
解决前的登陆代码
package com.example.logindemo.cotroller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.logindemo.Untils.JwtUntils;
import com.example.logindemo.commen.MessageConstant;
import com.example.logindemo.pojo.JwtProperties;
import com.example.logindemo.pojo.po.UserLogin;
import com.example.logindemo.result.Result;
import com.example.logindemo.service.UserLoginService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Param;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@Slf4j
@RequiredArgsConstructor
public class LoginController {
private final UserLoginService userLoginService;
private final JwtProperties jwtProperties;
private final RedisTemplate redisTemplate;
@PostMapping("/userLogin")
public Result userLogin( String username, String password) {
password=DigestUtils.md5DigestAsHex(password.getBytes());
LambdaQueryWrapper<UserLogin> userLoginLambdaQueryWrapper = new LambdaQueryWrapper<UserLogin>()
.eq(UserLogin::getUsername, username);
UserLogin userLogin = userLoginService.getOne(userLoginLambdaQueryWrapper);
if(userLogin==null && userLogin.getUsername().isEmpty()){
return Result.error("查询不到用户");
}
if (username.equals(userLogin.getUsername()) && password.equals(userLogin.getPassword())) {
//需要一个map集合 传什么 解析出来什么 一般传的是登录用户的id 我们传1
HashMap<String, Object> map = new HashMap<>();
map.put(MessageConstant.LOGIN_ID, userLogin.getId());
String token = JwtUntils.CreateJwt(map, jwtProperties.getSecretkey());
//设置redis缓存为1000天
redisTemplate.opsForValue().set(MessageConstant.TOKEN,token,1000,TimeUnit.DAYS);
return Result.success(token);
}
return Result.error("未知错误");
}
}
解决后 的登录代码 这里只放修改部分
解决后的 网关层过滤代码
现在 我们手动在数据库添加 一个用户
我们apifox进行登录接口的依次登录 ,然后观察 redis中的缓存数据
发现token 2 存在 我们也就设置成功了