JWT、 超详细、分析、token、鉴权、组成、优势 下

简介: JWT、 超详细、分析、token、鉴权、组成、优势 下

四、JWT 的其他问题

除了安全问题,JWT 还有许多其他需要考虑的问题。

1. 注销问题

因为 JWT 是无状态的,所以它的有效期完全由其本身决定,也就是说服务端无法让一个 token 失效。显然这是一个比较大的问题,对此也有诸多解决方案:

1.1 客户端主动注销

客户端直接删除存储 token 的 cookie

这种方案最为简单,操作的结果是无论客户端还是服务端都没有这个 token,可问题是,这个 token 并没有真正不可使用,而是处于一个游离态。

黑名单策略

客户端携带要注销的 token 访问一个注销接口,服务端把 token 加入一个黑名单。

此策略是否会出现黑名单过大的问题?

答案是不会,因为黑名单只需维护本身没有过期但又要使其无效的 token,过期的 token 就可以不用存在黑名单了。

1.2 服务端主动注销 \ 用户修改密码

把 token 和 uuid 用 key-value 对存储在 redis

这种方案看上去没问题,但是实际上,相当于自己实现了一次 cookie + session,JWT 就失去了『无状态』这一特性,从也会失去『无状态』特性带来的一系列的优点。

让每个用户都有一个 secret

前面讲到签发 token 的时候用到了 secret ,这种策略的思想就是让每个用户都有一个 secret,注销一个用户的时候修改其 secret,即可使其前面签发的 token 无法通过校验而失效。

这种策略上听上去不需要维护一个状态,但是实际上存在更大的问题。试想一下,第一种方案是通过 uuid 在已登录用户的 token 表中找到要注销的 token 注销。cookie + session 是通过 session_id 在已登录的用户的 session 表中找到其对应的 session 并删除来注销。而此方案是通过 uuid 在所有用户(而非已登录用户)中找到对于的 secret 修改来注销。这样看来会发现效率更低,因为查找范围更大了。

预黑名单

把要注销的用户的 uuid 和当前时间(TIME) 组成 key-value 对加入预黑名单,下次请求来时,若其 uuid 和黑名单中的对应,并且签发时间在 TIME 之前,则将其注销。这样查找范围就是未过期但又要注销的用户。并且在实现逻辑上这个预黑名单可以和签名的黑名单做到一起。

关于黑名单策略的补充:

有人可能会觉得黑名单也是一种状态,用这种策略实现的 JWT 并不能算纯正的无状态。这种说法没错,但是考虑每次要检索的数据范围可以得到下面一个关系:

未过期但要提前注销的用户或 token 数 < 所有已登录用户数 < 所有用户数

此处的『 < 』基本可以看成『远远小于』,所以黑名单策略虽然也算有状态,但是其维护的状态数也是特别小的。

可见 『黑名单』策略能够有效解决 JWT 的注销问题。

2. 续签问题

session 可以自动续签,那 token 如何实现自动续签呢?我们先仔细分析一下在 web 和 app 环境中,token 分别如何续签。先具体分析 web 续签和 app 续签分别是什么样的具体需求。

web

超过一段时间没有请求,需要重新登录,这个时间一般设置为 1-2 小时

app

超过一段较长的时间没有请求,需要重新登录,这个时间一般为 15-30 天

那这个需求可以如何实现呢?

2.1 方式一

服务端接管刷新

token 设置一个『过期时间』

token 过期后但是仍在『刷新时间』内时仍然可刷新

token 过期后超过『刷新时间』就不能再刷新,需重新登录

web

假设一个 token 的签发时间为 12:00,需求为 2h 未进行请求就要重新登录。则过期时间为 1h,刷新时间为 3h。

那么在 12:00 - 13:00 其都是可以正常使用的,如果在 13:00 - 15:00 进行请求,服务端自动换一个新 token 给客户端,达成续签。

如果 13:00 -15:00 之间没有进行请求,而是在 15:00 之后进行的请求,那么判断过期,需重新登录。

这样的话,最终的实现效果是:token 过期 2h 后需要重新登录 ,而不是 token 2h 未使用需要重新登录,导致的结果是,用户是 2 - 3h 未进行请求,需要重新登录。比设定的需求要多一个小时的不确定时间,但这也是没办法的办法了,至于会不会对业务造成影响,看具体需求吧,大多数的情况还是不会的。

app

和 web 端类似,设置成更长的时间周期即可。

对使用 Laravel 开发并使用 tymon/jwt-auth 这个插件的开发者,有个必须要注意的地方。

此处进行 token 的刷新并不是通过 refresh 这个操作获得新 token,因为这样 token 在不断的刷新过程中会达到一个刷新时间的上限。而上面的逻辑是每次都新签发一个 token,只要不断签就能够一直使用下去。 然后这里的旧 token 放入黑名单,黑名单有效期设置为『刷新时间』—— 3h。

当然如果开发者觉得这样不断签就能够一直使用不太好,那就可以设置更长的刷新时间,用 refresh 操作来获取新 token,刷新时间保证每次登陆得到 token 后,即使每次及时续签,最终也不会超过刷新时间。

然后这里又会出现一个新坑:

如果刷新时间设置为 14 天,过期时间设置为 2h。

token A 在 『 <= 14 天 』时刷新得到 token B,此时若再拿 token A 去请求刷新,肯定是不允许,否则 token 会出现『 1 变 N 』的问题,所以显然必须设置一个黑名单去放这些已过期但是又已经刷新过的 token。而这个黑名单的有效期范围应当为 token 的刷新期,即 14 天。然后你会发现对于每个用户每次登陆,需要维护的黑名单 token 数目最大可达 14 * 24 / 2 = 168 个,黑名单变得很大。

所以,如果要使用 refresh 操作,刷新时间务必是过期时间的尽量小的倍数。

2.2 方式二

每次请求 token 都进行一次刷新

token 设置一个过期时间

token 过期后无法再刷新

token 没必要设置刷新时间了

web

假设一个 token 的签发时间为 12:00,需求为 2h 未进行请求即过期。则设置有效期 2h,不需要设置刷新期。那么每次请求都会把一个 token 换成一个新 token。如果 2h 没有进行请求,那么上一次请求的到的 token 就会过期,需要重新登录。同样是不断签就能一直使用下去。

如果想要和上面一样,不希望永久续签,则设置一个刷新时间即可。这个刷新时间不会导致进一步膨胀。

app

和 web 端类似,设置更长时间即可。

然后又到了问题时间:

1.每次都刷新 token,带来的性能影响如何?

以前每次请求,需要进行一次 token 签名校验,而现在是要签发一个新 token,进行的都是一次签名运算,那么运算量即从 n 变成 2n。

其次,每次刷新都要把旧 token 加入黑名单,会导致黑名单特别大,远远比方式一的设置刷新期大。

2.每次都刷新 token,并发请求时会不会因为 token 刷新而导致只有一个请求成功?

答案是确实会导致这个问题,怎么解决呢?设置一个宽限时间,每次 token 刷新后,原来逻辑应该是立刻不可用,现在设置一个宽限时间,让其在 n 秒之内仍然可用即可。

总之,这种策略会导致花费的 CPU 运算翻倍,并导致巨大的黑名单,然后必须设置一个宽限时间以解决并发请求问题,至于宽限时间会不会带来安全问题,微乎其微吧。

2.3 黑名单膨胀的解决方案

上面讲到,对于方式一【限定不能一直续签】,会导致巨大的黑名单,对于方式二,总会导致一个更加巨大的黑名单。那有没有解决方案呢?当然是有的。

我们可以这么想,既然一个 token 进行了刷新,那么签发时间在这次刷新之前的即可认为无效。于是,和上面的『预黑名单』策略类似,我刷新时不是把一个 token 加入黑名单,而是把 uuid-refresh_time 组成 key-vakue 对加入黑名单,这样针对每个用户的每次登陆,要存储到黑名单中的条目数就从 N 个变成了一个。

但是这样还要考虑一个问题:就是一个用户开两个浏览器,在不同的时刻在同一个系统都登陆了(假设业务允许),那么一个浏览器的 token 刷新就可能会导致另一个浏览器登陆失效。所以存储在黑名单中的 key-value 应该再加一个 key 以代表每次登陆,并且这个 key 要在 JWT 的载荷中随着刷新一直传承。

基于以上的优化,黑名单的大小变成了:每个用户同时登陆的系统个数之和,就变的和 cookie + session 一样了。

比如,A 系统(假设 2h 过期时间,14 天刷新时间),你用一个浏览器登陆了你的账号,我用 Chrome 浏览器登陆了我的账号,然后我又用 QQ 浏览器再登陆我的账号,那么黑名单的大小就为 : 1 + 2 = 3

而对于方式一【限定不能一直续签】,黑名单的大小(最大):168 + 168 * 2

而对于方式二,黑名单的大小为:你在 2h 内请求的次数 x ,我在 Chrome 浏览器请求的次数 y,我在 QQ 浏览器请求的次数 z 之和,即:x + y + z

2.4 总结

如果要解决续签问题,方式一【可以一直续签】是个比较好的解决方案,虽然会带来一点小问题,但是并不会有太大的影响。方式二【限定不能一直续签】和 每次刷新会让黑名单的维护量和有状态差不多,但是有更高的安全性。

3. token 有没有必要每次刷新

我们先列举每次刷新 token 的优缺点:

优点:

能够实现续签

能够解决重放

更安全

缺点:

双倍的 CPU 消耗

几乎和有状态一样的空间消耗

必须设置宽限时间解决并发问题

上面讨论过,『续签』和『重放』都可以通过其他方式解决。只有『更安全』算半个痛点,为什么是半个痛点呢?因为如果采用 HTTPS 的话,那么盗取 token 的手段就只要以下几种办法:

破解 HTTPS

直接从你电脑上手抄过去

XSS【前面说到为了能够让 js 读取,不能设置 HTTPOnly】

只有第三种方法存在一点可能性。

所以,要不要每次刷新,还是根据各位的具体业务情况进行选择吧。

五、JWT 适合用来做什么

1. 无状态的 RESTful API

这个显然很适合。

2. SSO 单点登录

单点登录必须要实现的:

会话管理:通过黑名单和预黑名单解决

续签:通过签名的解决方案解决

可见,对 JWT 部署一些额外逻辑(黑名单,续签管理)即可让 JWT 在大部分场景代替 cookie + session。

六、JWT 与 Oauth2.0

Oauth 2.0 是干嘛的不再赘述,它与 JWT 其实并不是一个层面的东西。Oauth2.0 是一个方便的第三方授权规范,而 JWT 是一个 token 结构规范。只是 JWT 常用来登陆鉴权,而 Oauth2.0 在授权时也涉及到了登陆,所以就比较容易搞混。

但是在此,我要说的是,Oauth 2.0 其实可以和 JWT 结合使用。

以下是一个常见的 Oauth2.0 登陆返回:

{
    "access_token":"kag2geh11a3eh56e23hj",
    "expires_in":7200,
    "refresh_token":"jgko97cq4c8wn69j",
    "scope":"SCOPE" 
}

在 Oauth2.0 中,access_token 用来进行数据请求,而 refresh_token 用来刷新 access_token。每次刷新,上一个 access_token 就会失效,而 access_token 和 refresh_token 显然都没有记录任何状态,所以必须为服务端进行状态的维护。

把 JWT 和 Oauth2.0 结合后,可以得到这样的返回:

{
    "access_token":"xxx.yyy.zzz",
    "expires_in":7200,
    "refresh_token":"xxxxx.yyyyy.zzzzz",
    "scope":"SCOPE" 
}

进行结合后有如下优势:

Oauth2.0 的 token 也能够实现无状态(虽然也要用到黑名单)

Oauth2.0 的 token 也能够附带部分常用数据

前面讲到 JWT 续签,在需要限定不能一直续签的情形,可能会导致黑名单库膨胀,但是和 Oauth2.0 结合,通过 refresh_token 的机制,让黑名单库中 token 的有效期从 『刷新时间』又变回『过期时间』,从而解决了这个问题。

七、关于 token 十件必须知道的事

这是我从 Auth0 组织的这篇文章 10 Things You Should Know about Tokens 整理过来的:

  1. Token 获取到后需要保存起来以便下次使用,可以选择存储在 localstorage /sessionstorage/cookie
  2. Token 是包含有效期的,你必须部署一些逻辑来进行有效期的控制
  3. localstorage /sessionstorage 的跨域限制较 cookie 更为严格,推荐使用 cookie
  4. 在你进行异步请求时,浏览器一般都会发送预检请求(option),后端应对此部署相应的逻辑,为什么会有 OPTIONS 请求 - 云 + 社区 - 腾讯云
  5. 使用 cookie 可以轻松处理一个文件下载请求,但是 token 一般都是通过 XHR 方式进行请求的,所以你必须部署额外的逻辑。比如生成一个实时 ticket ,以 ticket 进行访问,然后校验,重定向,最后下载文件。
  6. 处理 XSS 比处理 CSRF 更容易(这一点我实在没看到他是什么个逻辑,大家可以去看看原文)
  7. token 在每次请求时都会被编码到请求中,所以请注意 token 的大小,不要编码过多数据
  8. 如果在 token 中编码敏感信息,请对 token 进行加密
  9. JSON Web Token 可以用于 Oauth2.0 的 Bearer Token 中,赋予 Oauth2.0 无状态的优势
  10. Token 不是银弹,请根据实际业务需要进行选择
目录
相关文章
|
1月前
|
JSON JavaScript 数据格式
jwt-auth插件实现了基于JWT(JSON Web Tokens)进行认证鉴权的功能。
jwt-auth插件实现了基于JWT(JSON Web Tokens)进行认证鉴权的功能。
44 1
|
3月前
|
存储 NoSQL 前端开发
jwt与redis,把生成的token放入redis中进行临时存储
jwt与redis,把生成的token放入redis中进行临时存储
77 0
|
26天前
|
负载均衡 Cloud Native 安全
云原生最佳实践系列 6:MSE 云原生网关使用 JWT 进行认证鉴权
本文档介绍了如何在 MSE(Microservices Engine)云原生网关中集成JWT进行全局认证鉴权。
|
3月前
|
SQL Java 数据库连接
Spring-Security & JWT 实现 token
Spring-Security & JWT 实现 token
34 0
|
3月前
|
JSON 安全 算法
JSON Web Token(缩写 JWT) 目前最流行、最常见的跨域认证解决方案
JSON Web Token(缩写 JWT) 目前最流行、最常见的跨域认证解决方案
163 0
|
4月前
|
存储 JSON 中间件
JWT json web token
JWT json web token
39 0
|
4月前
通过jwt基于token实现登陆认证通过jwt基于token实现登陆认证
通过jwt基于token实现登陆认证通过jwt基于token实现登陆认证
20 0
|
4月前
|
存储 缓存 NoSQL
【视频+源码】登录鉴权的三种方式:token、jwt、session实战分享
【视频+源码】登录鉴权的三种方式:token、jwt、session实战分享
|
19天前
|
安全 数据安全/隐私保护
Springboot+Spring security +jwt认证+动态授权
Springboot+Spring security +jwt认证+动态授权
|
1月前
|
前端开发 Java Spring
SpringBoot通过拦截器和JWT令牌实现登录验证
该文介绍了JWT工具类、匿名访问注解、JWT验证拦截器的实现以及拦截器注册。使用`java-jwt`库生成和验证JWT,JwtUtil类包含generateToken和verifyToken方法。自定义注解`@AllowAnon`允许接口匿名访问。JwtInterceptor在Spring MVC中拦截请求,检查JWT令牌有效性。InterceptorConfig配置拦截器,注册并设定拦截与排除规则。UserController示例展示了注册、登录(允许匿名)和需要验证的用户详情接口。
174 1