登录状态保持分析及使用springboot、redis和token整合案例(自定义注解拓展学习)

本篇文章为笔者结合了网上的几篇文章组合而成,大致从个人角度希望能说清楚状态保持的问题,并结合个人的实践进行其中一个方案的验证。
一、网站的接口按权限可以分为两种:
1、不需要登录就可以访问的,比如登录的接口
2、需要登录后才能访问的接口,比如登陆后的与用户信息相关的接口
二、为什么需要状态保持?
因为http协议是无状态的,也就是:服务器不知道两个请求是不是同一个用户发过来的;比如:有两个请求,一是登录请求,二是登录后访问个人资料,但是由于无状态的影响,所以,服务器不知道这两个请求是同一个人发送过来的。
所以解决http无状态的问题的解决方案就叫:状态保持
三、Web端是怎么做状态保持的?
Cookie和SessionId
Cookie的特点:会伴随着每次请求,在浏览器和服务器之间来回传递
登录状态保持分析及使用springboot、redis和token整合案例(自定义注解拓展学习)
文章图片

四、非web端如何实现状态保持?
由于cookie为web端独有的特征,而非web端不能依靠上述机制实现状态保持,但是可以借鉴上述思路,模拟相同的效果:token令牌机制。
登录状态保持分析及使用springboot、redis和token整合案例(自定义注解拓展学习)
文章图片

五、整体流程实现(附主要代码):
1、用户登录成功后,服务器可以通过自定义方法生成一个token,并将token存储在redis中,自定义设置过期时间。(笔者这里简易起见token使用随机生成的uuid,redis的存储形式为token:userId),之后将token和userId返回给客户端。

String token = CommonUtil.getUUID(); String ID = Long.toString(userInfo.getUsrid()); redis.setex(token,ID,REDIS_OPT_TIMEOUT); return ResponseEntityUtil.success("登陆成功",token + CONNECT_CHARS +ID);

2、客户端需要在拿到token后将token和userId存储在Header的Authrization字段中,客户端调用借口请求数据时,就会携带token一并发送到服务端(前端来实现,这里就放一些网上查到的方法:Ajax)
$.ajax({ type: "GET", url: "/access/logout/" + userCode, beforeSend: function(request) { request.setRequestHeader("Authorization", token); //将token和userId的拼接一同放置在authorization字段中 }, success: function(result) { } });

3、后端这里用了拦截器,拦截除了登录接口以外的所有请求,被拦截的请求就需要进到prehandle方法中进行权限验证(即token信息是否正确,首先根据请求中的token是否存在redis中来判断token是否正确和是否过期,如果没有问题再验证该token是否属于原用户,如果都正确则返回true,由于用户进行了操作,所以redis中的token过期时间需要更新。最后进入到请求的接口,完成接口内的逻辑。如果错误则返回false)(完整的拦截器可上网查询)
public void addInterceptors(InterceptorRegistry registry) { InterceptorRegistration authRegistry = registry.addInterceptor(authInterceptor); // 拦截路径 authRegistry.addPathPatterns("/**"); authRegistry.excludePathPatterns("/login"); }

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//从Header中得到token和userId,Header中目前Authorization字段为token+userId String authorization = request.getHeader(AUTHORIZATION); logger.info(authorization); if(CommonUtil.isEmpty(authorization)) return false; String[] param = authorization.split(CONNECT_CHARS); if(param.length != 2) return false; String token = param[0]; logger.info(token); String ID = param[1]; logger.info("================" + token); //验证该token是否存在 if(redis.isExists(token)){ //验证该token是否属于该用户 if(ID.equals(redis.get(token))){ redis.setex(token,ID,REDIS_OPT_TIMEOUT); return true; }else{ return false; } }else{ return false; } }

查找资料时发现了还有一种技术的可以使用,就是自定义注解,也是登录校验的一种典型方法,关于自定义注解的大致用法可以看下面的文章,写的清晰明了:
https://juejin.im/post/5d81a92c518825280e3e40dd#heading-4
后续:上网重新查阅了一下token的安全机制问题,感觉该方案确实不够安全,还有很多地方没有考虑到,等再消化总结一下再进行补充。
补充:按照师傅的意思,这个方案的主要的不安全的点是id在传输的过程中暴露了,如果客户端暴露在了攻击之下,攻击者拿到了id,可以在系统的其他模块通过sql注入用selectById进行其他信息的窃取。
解决办法:因此就在登录的模块进行加密,将ID加密后与token一同发到前端,redis中存的还是原ID,在需要验证的请求前端携带token,再将redis中的ID取出做与之前相同的加密,对比两次ID加密的结果进行验证。
问题:即便是这样也只能做到ID不暴露,当攻击者拿到token和value时同样可以模拟用户操作。但是如果客户端暴露在攻击之下时,任何安全措施都是无效的,因为攻击者在通晓双方通信的方式的情况下,服务端没有任何方法区分一个请求来自攻击者和真实的客户端。
所以重点在于:
1、防止客户端被攻破
2、客户端未被攻破的情况下保证安全(如中间人攻击)
第一种主要靠用户自己
第二种可以依靠HTTPS,在传输管道上就无法截获token
轮子哥的建议:可以把真实用户的公网IP地址hash一下写进token,每次需要校验时检查一下。(需要xss防御)
【登录状态保持分析及使用springboot、redis和token整合案例(自定义注解拓展学习)】参考文章:https://www.jianshu.com/p/af36620c7af8

    推荐阅读