本篇文章前置知识,jwt令牌有一定了解,对spring有一定了解,对登录接口的实现有一定了解。
Redis在登录接口中的应用
在我们平时写登录接口的时候,大家可能都会习惯性的说上一句,很简单哇。不就是前端像后端发送一个账号密码的请求,然后后端验证请求后,返回一个jwt令牌给前端,最后给令牌设置一个3天的过期时间,这样不就写完了吗。
但是大家可能忽略了一个我们平时很常见的问题, 就是在上述操作中,我们jwt的令牌过期时间是写死了的。想象一个场景,我们在一次oj竞赛网站中,竞赛时间要到了,我们点击提交,结果jwt的令牌时间刚好结束了,那我们不得骂死这个开发哇。
所以为了解决上述的一个问题,这里我们需要用其他组件来对返回的Token进行一个存储和叫校验,这个组件必须得满足,有存储功能,可以设置过期时间,同时因为会反复查询,所以必须要速度快。 Redis当然就是最佳人选啦。
这里讲一下大概得流程
开发思路
我们在后端生成jwt令牌的时候,将userId作为jwt中的存储内容进行传入,生成jwt令牌,同时将userId作为key, 敏感信息作为value 传入进入Redis数据库中,同时设置过期时间,要是用户调用接口的时候在Redis中进行一个查询,看是否能通过userId也就是Redis中的key来查到相应value,要是能查到,则表示当前用户为登录状态,同时在每一次通过查询之后,对接口进行一个剩余时间(redis中ttl)的判断,要是剩余时间不长,则对Redis中的数据进行延长,以达到一个重置登录状态的效果
这里我们的开发环节就写部分伪代码。
创建生成jwt令牌的工具类
这里就当大家对jwt有一定了解了, 因为jwt中payload 部分是可以被解码的。所以我们不能用jwt进行敏感信息的存储,但是我们可以用jwt来存储用户的身份标识。我们首先进行jwt令牌的创建其中claims就是jwt可以存储的对象,(网上可以搜到常用工具)
/**
* ⽣成令牌
*
* @param claims 数据
* @param secret 密钥
* @return 令牌
*/
public static String createToken(Map<String, Object> claims, String secret)
{
//点进去HS512下面的算法全是非对称加密
String token =
Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512,
secret).compact();
return token;
}
/**
* 从令牌中获取数据
*
* @param token 令牌
* @param secret 密钥
* @return 数据
*/
public static Claims parseToken(String token, String secret) {
return
Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
有了创建jwt的方法
我们假设后端已经完成了 验证用户账号密码的一个校验
此时调用createToken方法我们就可以进行Token的创建了 这里的Login对象为存储的敏感对象,我们可以自定义里面的字段
public String createToken(Long userId, String secret){
//创建jwt令牌, 令牌中存储userId
Map<String, Object> claim = new HashMap<>();
claim.put("userId", userId);
String token = JwtUtils.createToken(claim, secret);
//这里的Login对象为自定义对象,可以存储用户的敏感信息,比如用户身份之类的
Login login = new Login();
login.setSusceptible(1);
//最后2个参数为自己设置这里是伪代码
redisTemplate.opsForValue().set(userId, login, timeout, timeUnit);
return token;
}
完成上述步骤基本的开发工作其实已经完成了,此时我们每一次前端发来请求之后我们就可以进行验证啦,当时要是项目是分布式项目的话,可以在gateway网关中完成拦截,单体架构可以就使用spring自带的拦截器就好了
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//先获取请求参数
String url = request.getRequestURI();
// 跳过不需要验证的路径 登录 和 测试等等
if (......) {
return true;
}
//从http请求头中获取token 进行身份认证 这里getToken()为自定义的方法啊
String token = request.getToken();
//先判断是否有令牌
if (StrUtil.isEmpty(token)) {
throw new Exception("令牌不能为空")
}
//获取令牌中信息 解析 payload中信息
Claims claims;
claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析 payload中信息
//令牌解析失败,可能已经被篡改过了
if (claims == null) {
throw new Exception("令牌已过期或验证不正确!");
}
//解析成功之后
String userId = JwtUtils.getUserKey(claims); //获取jwt中的key
//redis中是否存在jwt来判断令牌是否过期
boolean isLogin = redisService.hasKey(userId);
//Redis中不存在前面存储的Login对象表示登录已经过期
if (!isLogin) {
throw new Exception("登录状态已过期");
}
//说明令牌正确
.......
//这里可以对用户的敏感数据进行一些处理
return true;
}
当前前面只是完成了jwt令牌正确性和完整性的一个校验。在处理完令牌之后还有一步很重要的内容就是判断当前令牌剩余的时候,要是剩余的时间少于一个规定值,我们就对令牌进行延长,要是长期不操作,jwt令牌就会失效。
我们来完善延长方法
private boolean exTime(HttpServletRequest request,String userid){
//获取Token
String token =
request.getHeader(HttpConstants.AUTHENTICATION);
//找一个临界值 当时间小于临界值的时候进行一个延长 这里设定剩余120分钟是进行延长
Long expire = redisService.getExpire(userid, TimeUnit.MINUTES);
//180为自己设定的一个时间 可以随便设置一个L行的数 要是小于这个时间就进行延长
if(expire != null && expire < 180L){
redisService.expire(userid, CacheConstants.REDIS_EXP, TimeUnit.MINUTES);
}
return true;
}
经过这一系列流程,登录接口的扩展就大致开发完成啦