JWT的基本介绍
JWT(JSON WEB TOKEN)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。
JWT一般用于客户端与服务端间认证用户身份信息。比如用户登录这种情况。
别的实现形式有cookie和session。 http是无状态协议,明文传输,如果将信息直接保存到cookie中,虽然能验证用户的身份,但是很容易被黑客窃取。 使用session,就是服务端会保存一个session,将sessionid发送给客户端,客户端保存到cookie中,之后再发送,用户信息就不会被黑客窃取了。但这样子如果只有一个服务器还行,如果有多台服务器,那么很可能会使用负载均衡策略,也就是请求过来,哪个较为空闲哪个进行服务。这样子前一秒可能还在用服务器A,下一秒就要用服务器B了,而B没有对应的session,就无法验证sessionid了。这种解决方法可以是多个服务端共同同步session,但这样资源消耗大,我们还可以使用redis来进行存储session,这样服务端收到sessionid后将id发给redis,再从redis中获取session。这样就解决了多服务器的问题。
但这种实现形式会随着用户量和访问量的增加,Redis中保存的数据会越来越多,开销就会越来越大,多服务间的耦合性也会越来越大,Redis中的数据也很难进行管理,例如当Redis集群服务器出现Down机的情况下,整个业务系统随之将变为不可用的状态。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。
JWT结构
JWT的结构分为三部分,第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。JWT 通常是这样的:xxxxx.yyyyy.zzzzz
head
head存储两部分信息:类型声明,说明这是个JWT;声明加密的算法是哪个,通常直接使用 HMAC SHA256。
payload
载荷就是存放有效信息的地方。 playload 也是 JSON 格式数据,其中包含了 Claims(声明,包含 JWT 的相关信息)。
Clamis包含三部分内容:
Registered Claims(注册声明) :预定义的一些声明,建议使用,但不是强制性的。
-
Public Claims(公有声明) :JWT 签发方可以自定义的声明,但是为了避免冲突,应该在 IANA JSON Web Token Registryopen in new window 中定义它们。
-
Private Claims(私有声明) :JWT 签发方因为项目需要而自定义的声明,更符合实际项目场景使用。
一些常见的注册声明:
-
iss(issuer):JWT 签发方。 -
iat(issued at time):JWT 签发时间。 -
sub(subject):JWT 主题。 -
aud(audience):JWT 接收方。 -
exp(expiration time):JWT 的过期时间。 -
nbf(not before time):JWT 生效时间,早于该定义的时间的 JWT 不能被接受处理。 -
jti(JWT ID):JWT 唯一标识。
playload部分默认不加密,所以不要将私密信息放入。
signature
该部分是对前两部分的签名,防止被篡改。 这个签名的生成需要用到:
- Header + Payload。
- 存放在服务端的密钥(一定不要泄露出去)。
- 签名算法。
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,这个字符串就是 JWT 。
如何防止被篡改 客户端会用head和playload和自己加密的密钥再生成一个新的signature,与收到的signature进行比较,如果相同,则说明内容没有改变,如果不相同,则说明内容被修改了。所以只要客户端的密钥没有泄漏,那么黑客就无法正确的修改signature,这样就可以防止JWT被篡改。
JWT的优点和存在的问题
优点
- 无状态:JWT本身含有用户的信息,服务端不需要专门进行存储。增大了系统的可用性和伸缩性,减轻了服务端的压力。
- 有效避免了CSRF攻击,CSRF攻击是跨站请求伪造,简单来说就是用你的身份去做一些不好的事情(发送一些对你不友好的请求比如恶意转账)。这种攻击需要cookie中的sessionid来实现。而JWT一般存储在localstorage而不是cookie,那么非法请求就无法携带JWT,这样就避免了CSRF攻击。
- 适合移动端使用:使用cookie的传输不适合移动端,而JWT只要能被移动端保存就能使用,而且JWT可以跨语言使用。
- 单点登录友好:使用 Session 进行身份认证的话,实现单点登录,需要我们把用户的 Session 信息保存在一台电脑上,并且还会遇到常见的 Cookie 跨域的问题。但是,使用 JWT 进行认证的话, JWT 被保存在客户端,不会存在这些问题。
存在的问题
- 由于无状态而导致的不可控。就比如说,我们想要在 JWT 有效期内废弃一个 JWT 或者更改它的权限的话,并不会立即生效,通常需要等到有效期过后才可以。再比如说,当用户 Logout 的话,JWT 也还有效。除非,我们在后端增加额外的处理逻辑比如将失效的 JWT 存储起来,后端先验证 JWT 是否有效再进行处理。
具体的实现方案:
- 将JWT保存到数据库中,但这种方法与使用JWT的初衷相违背。
- 黑名单,维护一个黑名单,将失效的JWT录入到黑名单中,之后再收到JWT就与黑名单中的内容比较。
- 修改密钥,为每个用户维护一个专用密钥,要想让JWT失效就修改密钥,但这样有明显的缺点:分布式结构中多个服务器都要修改,这就和session差不多了;用户多端登录,只在一端退出,但这样会导致其他端也退出要重新登录才行。
- 保持令牌的有效期限短并经常轮换,会导致用户登录状态不会被持久记录,而且需要用户经常登录
续签问题,JWT的有效期一般设置的都不长,那么 JWT 过期后如何认证,如何实现动态刷新 JWT,避免用户经常需要重新登录?
- 类似于 Session 认证中的做法 这种方案满足于大部分场景。假设服务端给的 JWT 有效期设置为 30 分钟,服务端每次进行校验时,如果发现 JWT 的有效期马上快过期了,服务端就重新生成 JWT 给客户端。客户端每次请求都检查新旧 JWT,如果不一致,则更新本地的 JWT。这种做法的问题是仅仅在快过期的时候请求才会更新 JWT ,对客户端不是很友好。
- 每次请求都返回新 JWT 实现简单但开销会比较大,尤其是在服务端要存储维护 JWT 的情况下。
- JWT 有效期设置到半夜 保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。
- 用户登录返回两个 JWT 第一个是 accessJWT ,它的过期时间 JWT 本身的过期时间比如半个小时,另外一个是 refreshJWT 它的过期时间更长一点比如为 1 天。客户端登录后,将 accessJWT 和 refreshJWT 保存在本地,每次访问将 accessJWT 传给服务端。服务端校验 accessJWT 的有效性,如果过期的话,就将 refreshJWT 传给服务端。如果有效,服务端就生成新的 accessJWT 给客户端。否则,客户端就重新登录即可。
双token的不足就在于:
- 需要客户端来配合;
- 用户注销的时候需要同时保证两个 JWT 都无效;
- 重新请求获取 JWT 的过程中会有短暂 JWT 不可用的情况(可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT);
- 存在安全问题,只要拿到了未过期的 refreshJWT 就一直可以获取到 accessJWT。