自刷新Token——后端部分

Posted 君语流年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自刷新Token——后端部分相关的知识,希望对你有一定的参考价值。

Token后台逻辑

​ 这个是我和我的好朋友【Samuel_luo。】思考如何优化一个登录方式的过程,在他的博客中已经介绍了Token的流程和每一步的设计思路,详细可看:自刷新token——前端部分

​ 我在这里会对后台实现逻辑进行阐述,为防止有人没看过我亲爱的【Samuel_luo。】的博客就直接阅读本文,我在此简单介绍一下整体的结构:token 的 结构为 tToken + sToken + step 其中tToken为原始token,可以再这个token中存储信息,完成之前token的任何操作。sToken是基于tToken不断迭代而成的,用来校验本token是否正确。而step为步数,用来声明sToken迭代的次数。token传输不为明码,使用AES加密,密钥的确定使用RSA加密传输(效仿https)。

​ 首先,介绍一下对sToken实现的迭代方式,在我们讨论时出现了两种方式:

  1. 使用版本号控制的sToken刷新,也就是说,其实sToken不需要改变,只是需要给一个位置来确定他的版本号就行了,如果版本号不在双方认同的范围之内,那么sToken就相当于失效了。
  2. 使用一种方式可以使得sToken不断发生改变,比如我对sToken+1,那么对于后台看到传过来的sToken之后只需要在自身的基础上,同样完成+1的操作,再比对两者是否相同就可以完成sToken的校验。

​ 相较于这两种方式,我们选择了后者,因为我们设计这个token的最初逻辑就是希望可以防止一些高手,可以获取token,并对token做出一些改变传回后台,使得绕过了后台的检测机制,来保证安全性。如果采用前者,就比较容易再多次请求中总结出规律。

​ 而对于后者的+1操作,我们的sToken为String型,如果说,我们检查完step后发现需要迭代两次,那么我们可以实现sToken+1+1 == sToken+2吗?从做法上来讲可行,从逻辑上来说不建议,一是因为增加破解成本,sToken+1+1 != sToken+2这种做法,可以使得找规律的难度增加。二是,如果遵循了相等的法则,那么sToken就可以反向推出tTkoen,这是一个很危险的操作。所以我们的迭代方式采用的是对字符串进行MD5操作。

​ 然后因为不允许sToken+1+1 == sToken+2,所以在迭代sToken版本的时间复杂度为O(n);这个n为服务端与客户端的step的差值,如果这个差值过大,其实是有问题的,而且会导致迭代版本的时间过长,占用的服务器资源过多。所以这个step的差值一定小于10(这是一个值,在实现上可以设置为另一个更符合自己项目的值)。对于服务端保存的token,这个是已经被校验完了的,失效的token,是为了保证数据库被入侵时,获取到的数据皆为失效的。所以step一定是客户端大于服务端,即0 < ClientStep - ServerStep < 10.

​ step,是在服务端生成一个随机数,从0到100000;

​ 从实现上来讲,需要开发的就是三种类型的接口:

  1. 登录接口切面
  2. 登出接口切面
  3. 正常请求接口切面

​ 为了更便捷的实现这三种功能,采用了切面的方式对请求进行监管,利用properties注入监管函数的切面JoinPoint。切面如何实现的呢,在本文不再阐述了,有兴趣的可以自行了解一下。

登录接口切面

​ 对于登录来说,可以分为两种类型,一种是账号密码登录,一种是利用token自动登录,这两种的触发条件可以倚靠传入请求的header中有无token做出判断。当请求参数传来时,一定会携带一个叫做random-client的请求头,这是我们作为之后加密token的AESKey的一部分,由可客户端提供一部分,另一部分则需要服务端生成random-server,然后以一种规则凭借成AESKey = random-client + random-server;使用账号密码登录呢是切面实现上最简单的,毕竟切面就是为了增强函数。使用token自动登录,则需要我们主动向请求中传输信息,在这里有两中实现方式:一种为使用field修改函数的参数;另一种为使用ThreadLocal传入参数。我的实现方式为后者,相比较起来,这个更容易实现,而且在springboot中每个请求都会使用一个独立的线程,每个线程的ThreadLocal所对应的value都可以是不受影响的。

​ 到现在为止,我们获取信息的操作已经完成了,只需要向数据库验证账号密码,但是这不是我们这个token所需要讨论的内容。

​ 对于返回的信息,除了请求的正常返回,还需要携带一些信息在响应header中:

  • token:首先需要将tToken+sToken + step,作为token返回,这里需要使用AESKey加密。
  • random-server:是ASEKey的一部分,用RAS加密后返回给前端,前端获取后可以组成AESkey解密token。
  • flag:这是一个标志位,因为在上面的逻辑里没提到,我会在后面的文章里详细说明。

登出接口切面 和 正常请求接口切面

​ 对于登出接口和正常请求接口的切面,他们的判断逻辑是相同的,只是操作逻辑会有不同,所以放在一起来说。

​ 对于这里的判断逻辑,首先会获取请求的header,这里是获取token。在这里的token中会发现,token是被AES加密的,所以我们需要知道,这个token对应的ASEKey是什么,那么我们是不是还需要从header中获取AESKey呢?

​ 我们不应该相信前端传来的信息!所以我们用一个标志位存储信息,并通过标志位的反方式获取到这个AESKey,这也是在上面出现了一个flag位的原因。我们会在登录中,将信息存储到数据库中,我们会设置两个key对应的value:

  1. flag->token
  2. flag->AESKey

​ 所以我们需要从请求中获取flag后,从我们的数据库中获取相对应的信息,然后我们首先会通过AESKeyu解密出请求头中的token,将后台的token中的tToken与传来的tToken经行对比,若tToken不相同,则不能通过,再之后进行sToken的迭代就按测,若不相同,则不能通过。

​ 要通过了token验证,那么就会正常进行请求的回应。

​ 最后若登出切面,那么会将数据库中存储的相关flag对应的数据删除;若正常请求切面,会将验证成功的token储存入数据库中。

扩展

​ 既然有了flag,我们为什么不将前端传回来的这个token改为stoken+step的形式呢?我们可以获取数据库中的token,再比对stoken是否生效后,就可以完成自动登录了。

​ 还是那个道理,我们不能相信前端传来的任何信息。对于以上的流程中,我们不相信flag是真实的,但是我们相信这个flag获得的token是真实的,但是我们不能确保这个token校验了stoken成功后,就能确定这个flag和stoken对上了。因为stoken我们采用的是MD5算法,本质是Hash算法。那么就存在冲突的可能性,也就是说存在两个不同的token加密出一个相同的stoken。

​ 所以,在这个流程中,tToken的存在不单单是完成自动登录,还有为了确保前端传来的flag的真实性。

以上是关于自刷新Token——后端部分的主要内容,如果未能解决你的问题,请参考以下文章

JWT生成token及过期处理方案

无感刷新token

uni-app 无痛刷新 token 方法

实现无感刷新token我是这样做的

实现无感刷新 token 我是这样做的

简单介绍vue获取token实现token登录的示例代码