点击上方“Java基基”,选择“设为星标”
做积极的人,而不是积极废人!
每天 14:00 更新文章,每天掉亿点点头发...
源码精品专栏
JWT 现在应该是最流行,采用最多的跨域认证解决方案了。因此,在面试过程中,也有很多人把它作为一个考题。基于此,今天,我们一起来聊聊 jwt 的优缺点和它的双 token 机制。
何为token
token即为令牌,是服务器生成的一串字符串,作为客户端向服务器进行请求的“通行证”。在客户端进行初次登陆后由服务器返回,之后的每次请求只需要携带token进行请求即可,而无需携带密码等敏感信息
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
为何token
token可以减少敏感信息在网络间的传递
因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA、JavaScript、NodeJS、PHP等很多语言都可以使用
JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。它不需要在服务端保存会话信息, 所以它易于应用的扩展
基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。
项目地址:https://github.com/YunaiV/onemall
基于token的登录流程
基于token的请求流程
token的结构
先来看看生成的jwt token的样子:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJleHAiOjE2MTA1NDcyMzQsImRhdGEiOiJkYXRhIiwiaWF0IjoxNjEwNTQwMDM0fQ.
Qno5UbhzAvlN6QAXpbqpOkeTMt4qEQvmY50Yh87JD74
token分为3个部分,每个部分由一个字符.
相连:
第一部分(头部/header):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
头部与http的头部差不多作用,是用来存放声明信息的,其主要有:
{
"typ": "JWT", // 这里声明了类型,即JWT
"alg": "HS256" // 这里声明了加密算法,即HS256加密算法
}
在头部信息确定好之后,将这些信息经过base64加密,构成了如上所示的token第一部分
第二部分(载荷/payload):
eyJleHAiOjE2MTA1NDcyMzQsImRhdGEiOiJkYXRhIiwiaWF0IjoxNjEwNTQwMDM0fQ
载荷同http的body差不多功能,用来存放有效信息的,其中包含的信息字段种类及其含义如下:
{
exp, // 过期时间,这个过期时间必须要大于签发时间
iat, // 签发时间
[data,] // 如果你想的话,可以塞一些非敏感信息
[iss,] // 签发者
[sub,] // 面向的用户
[aud,] // 接收的一方
[nbf,] // 定义一个时间,即在该时间之前,这个jwt是不可用状态
[jti] // 唯一身份标识,主要用来作为一次性token,从而回避重放攻击
}
在载荷的信息确定好之后,将这些信息经过base64加密,构成了如上所示的token第二部分
第三部分(签证/signatrue):
Qno5UbhzAvlN6QAXpbqpOkeTMt4qEQvmY50Yh87JD74
签证信息由3个部分组成:
上文所述加密后的token第一部分
上文所述加密后的token第二部分
自定义的混淆字符串
上述3个部分经过指定算法加密之后输出的字符串就构成了如上所示的token第三部分。
jwt的原理
通俗的来讲就是第三部分是个大杂烩,把上述3个部分丢到加密算法这个大锅里煮(加密),煮出来的菜就是第三部分。
*「自定义的混淆字符串是很重要的部分,需要好好保存在服务端,生成与验证token就靠它了」 *
token注意事项
由于token的组成关系,前2部分只用了base64这种可以随意解密的东西加密的,而第三部分作为凭证留在了客户端,因此,「token里不能存储敏感信息」
双token验证机制
场景设置
在基于token验证登录态这个情境下,可以想象一个场景,你在使用app或者在网页上进行操作时,你的token突然就过期了,然后只好被迫停止现在正在进行的操作跳转到登录页进行重新登录操作,这就非常的智熄了,这带给用户,特别是经常使用或正在进行某个操作的用户,一种非常不好的体验。这就是单token验证登录的一个缺点。因此对于经常/正在使用或经常/正在进行操作的用户(我称之为活跃用户)就不应当跳转到登录页面进行登陆操作(除了某些敏感系统或包含敏感信息进行敏感操作的网页、app、系统),对于这个缺点的解决,这里介绍一个双token的验证机制
或许会有人说可以把token的有效期设的很长啊,但是记住,千万别那么做,这会让token的安全性变得无法保证,在我的理解中,token本就是为了减少未经保护的敏感信息在网络中的传递而设置的,同时也方便了登录态的检验,但是如果token设置的过期时间相当的长,那么token和帐号的安全性变得几乎没有意义,只要任何一个人拿到了token,就可以在一个相当长的时间内对你的帐号动手动脚(怎么一股NTR的味道···?)
何为双token验证
顾名思义,就是在登陆操作之后由服务端返回两个token:accessToken和refreshToken,在之后的验证登录态的操作中使用这两个token进行验证,其中accessToken的过期时间相当短,refreshToken的过期时间「相对」 于accessToken而言相当长,且会不断的刷新,每次刷新后的refreshToken都是不同的
双token验证的优点
通过上面的描述也可以或多或少的看出双token验证机制的优点了:
accessToken的存在,保证了登录态的正常验证,因其过期时间的短暂也保证了帐号的安全性
refreshToekn的存在,保证了用户(即使是非活跃用户)无需在短时间内进行反复的登陆操作来保证登录态的有效性,同时也保证了活跃用户的登录态可以一直存续而不需要进行重新登录,其反复刷新也防止某些不怀好意的人获取refreshToken后对用户帐号进行动手动脚的操作(拒绝NTR.jpg)
双token检验流程
首先进行正常的登录操作,在后台服务器验证账号密码成功之后返回2个token:accessToken和refreshToken。在进行服务器请求的时候,先将Token发送验证,如果accessToken有效,则正常返回请求结果;如果accessToken无效,则验证refreshToken。此时如果refreshToken有效则返回请求结果和新的accessToken和新的refreshToken。如果refreshToken无效,则提示用户进行重新登陆操作。
流程图如下:
token的时间设置
token的时间设置需要看需求进行划分区别设置:
PC网络应用
对于网络应用程序而言,由于token可以直接直观地获取到,因此不管是accessToken还是refreshToken为了安全起见,其过期时间都不应该设置得很长,且需要不停地更换token,因此PC网络应用的accessToken一般设置为2h过期,而refreshToken设置为1天到2天比较好,不足1天也是可以的,如果设置的时间比较短就在活跃期间时常刷新freshToken就好了,如果设置的时间比较长,就只需要设置一个阈值(比如7day的refreshToken设置一个6day阈值),在refreshToken小于等于这个阈值的时候就进行刷新refreshToken就好了。
手机应用
对于手机APP应用而言,登录操作一般只做一次,因此token的过期时间必是无限,即不会过期,不过为了安全起见(比如防止你丢手机),token应该以某种程度上对用户可见(比如在安全中心里检验了身份之后可以让你看到哪些设备有token,即哪些设备会被允许登录)并可让用户对其进行一定程度上的操作(比如你手机丢了,然后登录安全中心移除那个手机的token,也就是移除那个手机的登陆权限,从而使那个手机的应用上的你的帐号强制下线)
无效的Token的处理
对于频繁更换的Token,如何处理旧的未过期的而又无效的refreshToken,以下提供了几个思路:
1) 简单地从浏览器中移除token就好了
显然,这种方式对于服务器方面的安全而言并没有什么卵用,但它能通过移除存在的token来阻止攻击者(比如,攻击者必须在用户下线之前窃取到token)
2) 制作一张token黑名单
在移除了浏览器存储的token后如果还想要再严格点,就只能在服务器上制作一张已经无效但是没过期的token的黑/白名单了,在每次请求中都操作数据库进行token的匹配,并以某种方式进行维护(不管是黑名单的定期删除维护也好,白名单的无效时删除也好),不过显然这种方式还是违背了token无状态的初衷,但是除此之外也没别的办法。
存储可以按照userId—token的方式存储在数据库中(当然也可以按你喜欢添加其他字段标明其他信息,比如说mac地址啦,是手机还是电脑啦,设备型号啦,巴拉巴拉巴拉····),白名单的话直接存储有效的token,在需要token无效的逻辑中删除指定token即可(比如刷新token的时候把旧的无效的但未过期的删掉)。而如果是黑名单的话就需要你定期去删除其中已经过期的token了。
而验证的话除了要去数据库名单里匹配之外还需要验证token本身的有效性。
3)只需要将token的过期时间设置的足够短就行了
JWT 双 token 存在的意义
通常在使用JWT的时候会设置access_token和refresh_token两个token,access_token有效期较短,refresh_token有效期较长,当access_token过期之后,如果refresh_token没有过期则可以换取新的access_token。
Refresh Token 是有意义的,相当于一个会话流 Token。
拿到Access Token可以访问,但你的Access Token如果过期时间短,那么这时需要刷新Refresh Token会重新生成的新的 Access Token和Refresh Token。这时历史的Access Token就过期了。
如果中间有人监听,到期了,除非他也用Refresh Token去更新新的Token,才能正常访问,但这时用户就会出现登陆问题。
如同黑客访问你的远程桌面,你的远程桌面就会锁定,形成了互斥,这时你就能发现异常了,如果结合服务器端程序判断,很容易发现这种异常,因为用户反复掉线,这时可以增加手机验证码验证登陆。
也就是说监听者需要实时获取到Token才能保持一直在线。
Refresh Token在用户和服务器通信过程中,如同不断变换Token,只能在一段时间内有效,增加了安全性。
如果每次访问换一个Token,那么监听者就难处理了,因为一旦用现在token去访问服务器,下一次的token就不样,用户端就会被踢出(这时服务器可以用短信验证功能来验证真实用户),用户被踢出很容易知道有异常。无缝处理需要在网络链路上实时处理,需要构建实时自动处理程序,这样被监听破解的难度就提高很多。
如果有 https 保护,就更加安全了。而且现在很多系统都要求必须要用 https,如果你不用,漏洞扫描这一关你就过不了!
欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:
已在知识星球更新源码解析如下:
最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。
提供近 3W 行代码的 SpringBoot 示例,以及超 6W 行代码的电商微服务项目。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)