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

本篇文章为笔者结合了网上的几篇文章组合而成,大致从个人角度希望能说清楚状态保持的问题,并结合个人的实践进行其中一个方案的验证。

一、网站的接口按权限可以分为两种:
1、不需要登录就可以访问的,比如登录的接口
2、需要登录后才能访问的接口,比如登陆后的与用户信息相关的接口

二、为什么需要状态保持?

因为http协议是无状态的,也就是:服务器不知道两个请求是不是同一个用户发过来的;比如:有两个请求,一是登录请求,二是登录后访问个人资料,但是由于无状态的影响,所以,服务器不知道这两个请求是同一个人发送过来的。

所以解决http无状态的问题的解决方案就叫:状态保持

三、Web端是怎么做状态保持的?
Cookie和SessionId
Cookie的特点:会伴随着每次请求,在浏览器和服务器之间来回传递

在这里插入图片描述

四、非web端如何实现状态保持?

由于cookie为web端独有的特征,而非web端不能依靠上述机制实现状态保持,但是可以借鉴上述思路,模拟相同的效果: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防御)

参考文章:https://www.jianshu.com/p/af36620c7af8

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值