admin管理员组文章数量:1595563
前言
在SpringBoot集成SpringSecurity(一) 入门一文中我们曾经提到做会话管理控制的有两种方式(如果对其不了解的话,建议你去上一篇去看一下):
- 基于session方式;
- 基于token方式;
在之前的文章中(security系列)我们采用的会话管理方式皆为session的方式,而在本文中,我们将采用token的方式,顺便我们可以将两种方式的优劣势做一个对比。
本文代码已上传至GitHub
https://github/wanglongsxr/springsecurity.git
基于session方式的身份验证
-
用户向服务器发送用户名和密码进行身份验证;
-
验证服务器后,相关数据(如用户角色,登录时间等)将保存在当前会话中;
-
服务器向用户返回session_id;
-
session信息都会写入到用户的Cookie;
-
用户的每个后续请求都将通过在Cookie中取出session_id传给服务器;
-
服务器收到session_id并对比之前保存的数据,确认用户的身份。
这种模式最大的问题是,没有分布式架构,无法支持横向扩展。如果使用一个服务器,该模式完全没有问题。
但是,如果它是服务器群集或面向服务的跨域体系结构的话,基于session的认证会出现一个问题,每个应用服务都需要在session中存储用户身份信息,通过负载均衡将本地的请求分配到另一个应用服务需要将session信息带过去,否则会重新认证。
这个时候,通常的做法有下面几种:
- Session复制:多台应用服务器之间同步session,使session保持一致,对外透明。
- Session黏贴:当用户访问集群中某台服务器后,强制指定后续所有请求均落到此机器上。
- Session集中存储:将Session存入分布式缓存中,所有服务器应用实例统一从分布式缓存中存取Session。(我们项目采用的就是此方式)
总体来讲,基于session认证的认证方式,可以更好的在服务端对会话进行控制,且安全性较高。但是,session机 制方式基于cookie,在复杂多样的移动客户端上不能有效的使用,并且无法跨域,另外随着系统的扩展需提高 session的复制、黏贴及存储的容错性。
基于token方式的身份验证
服务端不用存储认证数据,易维护扩展性强, 客户端可以把token 存在任意地方,并且可 以实现web和app统一认证机制。
其缺点也很明显,token由于自包含信息,因此一般数据量较大,而且每次请求 都需要传递,因此比较占带宽。另外,token的签名验签操作也会给cpu带来额外的处理负担。
引入JWT
什么是JWT
在上文中我们介绍了session与token两种方式的区别,那么token方式怎么实现呢?
可以这么说,JWT是token实现方式的一种,另外的有:自定义token,oauth2等…
JWT是一种认证协议,JWT提供了一种用于发布接入令牌(Access Token),并对发布的签名接入令牌进行验证的方法。 其中令牌(Token)本身包含了一系列声明,应用程序可以根据这些声明限制用户对资源的访问。
通俗点来讲:服务器认证以后,会根据用户的相关信息生成一个 JSON 对象,发回给用户,然后用户与服务端通信的时候,服务器通过这个JSON对象去认定用户身份
一个jwt的实际案例;
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
它由3部分组成,分别为:
- header(头)
- payload(有效载荷)
- signature(签名)
三者之间使用“.”链接,格式如下:
header.claims.signature
将它们写成一行如下:
很多人肯定很好奇为什么一大堆看似无规律字母包含了用户的信息,这是因为为了安全的在url中使用,所有部分都base64 URL-safe进行编码处理。
Header头部分
头部分简单声明了类型(JWT)以及产生签名所使用的算法。
{
"alg" : "AES256",
"typ" : "JWT"
}
payload有效载荷
此部分时用于存储用户的详细信息。
有些情况下,我们很可能要在一个服务器上实现认证,然后访问另一台服务器上的资源;或者,通过单独的接口来生成token,token被保存在应用程序客户端(比如浏览器)使用。
JWT指定七个默认字段供选择: 1. iss:发行人 2. exp:到期时 3. sub:主题 4. aud:用户 5. nbf:在此之前不可用 6. iat:发布时间 7. jti:JWT ID用于标识该JWT
除以上默认字段外,我们还可以自定义私有字段
一个简单的声明(payload)的例子:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意: 编码的一个特点:编码和解码的整个过程是可逆的。得知编码方式后,整个 jwt 串便是明文了,所以一定不能够携带敏感数据如密码等信息的。
Signature签名
混淆Header和payload,保证上边两部分信息不被篡改。
如果尝试使用Bas64对解码后的token进行修改,签名信息就会失效。一般使用一个私钥(secret)通过特定算法对Header和payload进行混淆产生签名信息,所以只有原始的token才能于签名信息匹配。
混淆公式为:HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret)
secret特别重要,相当于jwt对象是个锁,而secret就是个钥匙,所以,永远不要把私钥信息放在客户端(比如浏览器)。
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。
JWT的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization
字段里面。
Authorization: Bearer <token>
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
JWT工作流程
步骤的标注已经很清楚了,在这里我们详细说一下第四点跟第五点这里
在携带jwt请求的时候,过滤器会拦截所有的请求,而在过滤器这里要做的内容很多,如下:
- 首先先去放行不需要jwt的页面
- 其次判断request请求是否携带jwt,否则返回登录页;
- 然后判断jwt是否过期,否则返回登录页;
- 验证token是否合法,是否存在,用户是否存在;
- token的续签问题(后面着重讲述)
JWT的利弊
优点:
-
无状态;
其实怎么理解这个无状态呢?token包含了我们身份验证的所有信息,从而在服务端无需生成session_id,也就是说服务端无需维护状态。对于分布式系统来讲(多台服务器组成的集群),等同于无需同步各个服务器之间的状态。
-
有效避免了CSRF 攻击;
避免了非法分子通过cookie或者session未过期的时间段登录系统
-
不依赖认证服务即可完成授权,降低服务器查询数据库的次数;
token校验是通过算法来完成校验,避免了数据库查询次数
-
无需储存在服务器,降低内存开销,支持跨域访问;
-
适合移动端应用。
解决移动端无cookie的场景
缺点:
- 令牌较长,占存储空间比较大;
- Token不能撤销,不能作废;
- 不应存储敏感信息。
JWT的使用场景
-
一次性的验证;
比如GitHub账号之后,会收到一封激活邮箱。注意,当我们点开连接的适合,并不需要登录GitHub。
这类场景的特点即为:
- 时效性(有效时间内,比如4小时之内);
- 不可篡改性;
-
服务器集群或者分布式系统;
无需维护状态,保证用户信息在各服务器之间的通用性;
-
服务器与服务器之间的认证。
比如oauth2框架内部的认证体系。
对JWT引发的思考
JWT 不是万能的,在实际使用中, JWT 也会带来诸多问题,这些问题也着实令人头疼:
注销登录等场景一系列问题
类似的场景有:
- 退出登录;
- 修改密码;
- 服务端修改了某个用户具有的权限或者角色;
- 用户的账号被删除/暂停;
- 用户由管理员注销;
而产生这一系列问题原因是token的无法撤销以及无法更改。
服务端没有存储起来,所以即使客户端删除了 jwt,但是该 jwt 还是在有效期内,只不过处于一个游离状态。如果后端不增加其他逻辑的话,它在失效之前都是有效的
那么该如何解决这个问题呢?
-
引入第三方存储,管理 jwt 的状态;
以 jwt 为 key,实现去 redis 一类的缓存中间件中去校实现。如果需要让某个 token 失效,就直接从 redis 中删除这个 token 即可。但是,这样会导致每次使用 token 发送请求都要先从 DB 中查询 token 是否存在的步骤,而且违背了 JWT 的无状态原则。
-
黑名单机制;
和上面的方式类似,使用内存数据库比如 redis 维护一个黑名单,如果想让某个 token 失效的话就直接将这个 token 加入到 黑名单 即可,token过期删除即可。然后,每次使用 token 进行请求的话都会先判断这个 token 是否存在于黑名单中。
-
修改密钥 (Secret);
不建议这种,原因如下:
- 如果系统是分布式系统,一旦修改密钥,就意味着你需要在多个服务器同步密钥,这样与session的实现无异;
- 如果用户同时在两个浏览器打开系统,或者在手机端也打开了系统,如果它从一个地方将账号退出,那么其他地方都要重新进行登录,这是不可取的。
-
保持令牌的有效期限短并经常轮换 ;
不建议,用户体验效果极差。需要用户经常登录。
-
对于修改密码后 token 还有效问题的解决方案:
使用用户的密码的哈希值对 token 进行签名。因此,如果密码更改,则任何先前的令牌将自动无法验证。
token续签
对于传统的session方案,session 有效期 30 分钟,30 分钟内如果有访问,session 有效期被刷新至 30 分钟;
而jwt自身的设计(token的无法撤销以及无法更改)感觉天然不支持续签;即使他支持exp这个属性,但是token一旦生成,就无法更改。
那么如何解决呢?有如下几个方案:
-
每次请求刷新 jwt;
不推荐,这也太暴力了,会带来性能问题。
-
只要快要过期的时候刷新 jwt;
只在最后的几分钟返回给客户端一个新的 jwt,但是这样做就带点运气成分,如果用户最后几分钟没有操作,导致jwt未刷新,进而重新登录;
-
使用 redis 记录独立的过期时间;
在 redis 中单独会为每个 jwt 设置了过期时间,并规定死一个刷新时间点,每次访问时,先判断距离过期时间还有多久,与规定好的时间点做比较,如果小于规定好的时间点,便刷新 jwt 的过期时间,若 jwt 不存在与 redis 中则认为过期。
-
用户登录返回两个 token
第一个是 acessToken ,它的过期时间 token 本身的过期时间比如半个小时,另外一个是 refreshToken 它的过期时间更长一点比如为1天。客户端登录后,将 accessToken和refreshToken 保存在本地,每次访问将 accessToken 传给服务端。服务端校验 accessToken 的有效性,如果过期的话,就将 refreshToken 传给服务端。如果有效,服务端就生成新的 accessToken 给客户端。否则,客户端就重新登录即可。该方案的不足是:
- 需要客户端来配合;
- 用户注销的时候需要同时保证两个 token 都无效;
- 重新请求获取 token 的过程中会有短暂 token 不可用的情况(可以通过在客户端设置定时器,当accessToken 快过期的时候,提前去通过 refreshToken 获取新的accessToken)。
针对于JWT的安全性考虑
为了防止用户 JWT 令牌泄露而威胁系统安全,需要注意以下几点或者完善系统功能方向:
-
JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
-
为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
-
监控请求频率:如果 JWT 密令被盗取,攻击者或通过某些工具伪造用户身份,高频次的对系统发送请求,以套取用户数据。针对这种情况,可以监控用户在单位时间内的请求次数,当单位时间内的请求次数超出预定阈值值,则判定该用户密令是有问题的。例如 1 秒内连续超过 5 次请求,则视为用户身份非法,服务端终止请求并强制将该用户的 JWT 密令清除,然后回跳到认证中心对用户身份进行验证。
-
敏感操作保护:在涉及到诸如新增,修改,删除,上传,下载等敏感性操作时,定期(30分钟,15分钟甚至更短)检查用户身份,如手机验证码,扫描二维码等手段,确认操作者是用户本人。如果身份验证不通过,则终止请求,并要求重新验证用户身份信息。
-
对于加密算法,尽量采用rsa 这种非对称加密方式。
-既然是加密,自然是不希望别人知道我的消息,只有我自己才能解密,所以公钥负责加密,私钥负责解密。这是大多数的使用场景,使用 rsa 来加密。
- 既然是签名,自然是希望别人不能冒充我发消息,只有我才能发布签名,所以私钥负责签名,公钥负责验证。
所以,在客户端使用 rsa 算法生成 jwt 串时,是使用私钥来“加密”的,而公钥是公开的,谁都可以解密,内容也无法变更(篡改者无法得知私钥)。
总结
上文中我们详细的总结了jwt的优缺点以及适用场景,同时也简单的对比了传统session的登录方式,至于采用哪种方案,就需要看实际场景了,毕竟这两个方案并不是万能的。
如你已对上文内容了解,又缺少实践操作的话,代码已上传至GitHub。欢迎你的star
另:该实践采用的是使用 redis 记录独立的过期时间的方式实现了token续签的场景(建议有security基础观看)
https://github/wanglongsxr/springsecurity.git
Reference
JSON Web Token 入门教程
JSON Web Token - 在Web应用间安全地传递信息
深入理解JWT的使用场景和优劣
JWT 身份认证优缺点分析以及常见问题解决方案
JWT 也不是万能的呀,入坑需谨慎!
SpringSecurity实战-fulilnlin
版权声明:本文标题:浅谈JWT 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1728225564a1150135.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论