JWT的学习记录

154 阅读7分钟

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的优点和存在的问题

优点

  1. 无状态:JWT本身含有用户的信息,服务端不需要专门进行存储。增大了系统的可用性和伸缩性,减轻了服务端的压力。
  2. 有效避免了CSRF攻击,CSRF攻击是跨站请求伪造,简单来说就是用你的身份去做一些不好的事情(发送一些对你不友好的请求比如恶意转账)。这种攻击需要cookie中的sessionid来实现。而JWT一般存储在localstorage而不是cookie,那么非法请求就无法携带JWT,这样就避免了CSRF攻击。
  3. 适合移动端使用:使用cookie的传输不适合移动端,而JWT只要能被移动端保存就能使用,而且JWT可以跨语言使用。
  4. 单点登录友好:使用 Session 进行身份认证的话,实现单点登录,需要我们把用户的 Session 信息保存在一台电脑上,并且还会遇到常见的 Cookie 跨域的问题。但是,使用 JWT 进行认证的话, JWT 被保存在客户端,不会存在这些问题。

存在的问题

  1. 由于无状态而导致的不可控。就比如说,我们想要在 JWT 有效期内废弃一个 JWT 或者更改它的权限的话,并不会立即生效,通常需要等到有效期过后才可以。再比如说,当用户 Logout 的话,JWT 也还有效。除非,我们在后端增加额外的处理逻辑比如将失效的 JWT 存储起来,后端先验证 JWT 是否有效再进行处理。

具体的实现方案:

  1. 将JWT保存到数据库中,但这种方法与使用JWT的初衷相违背。
  2. 黑名单,维护一个黑名单,将失效的JWT录入到黑名单中,之后再收到JWT就与黑名单中的内容比较。
  3. 修改密钥,为每个用户维护一个专用密钥,要想让JWT失效就修改密钥,但这样有明显的缺点:分布式结构中多个服务器都要修改,这就和session差不多了;用户多端登录,只在一端退出,但这样会导致其他端也退出要重新登录才行。
  4. 保持令牌的有效期限短并经常轮换,会导致用户登录状态不会被持久记录,而且需要用户经常登录

续签问题,JWT的有效期一般设置的都不长,那么 JWT 过期后如何认证,如何实现动态刷新 JWT,避免用户经常需要重新登录?

  1. 类似于 Session 认证中的做法 这种方案满足于大部分场景。假设服务端给的 JWT 有效期设置为 30 分钟,服务端每次进行校验时,如果发现 JWT 的有效期马上快过期了,服务端就重新生成 JWT 给客户端。客户端每次请求都检查新旧 JWT,如果不一致,则更新本地的 JWT。这种做法的问题是仅仅在快过期的时候请求才会更新 JWT ,对客户端不是很友好。
  2. 每次请求都返回新 JWT 实现简单但开销会比较大,尤其是在服务端要存储维护 JWT 的情况下。
  3. JWT 有效期设置到半夜 保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。
  4. 用户登录返回两个 JWT 第一个是 accessJWT ,它的过期时间 JWT 本身的过期时间比如半个小时,另外一个是 refreshJWT 它的过期时间更长一点比如为 1 天。客户端登录后,将 accessJWT 和 refreshJWT 保存在本地,每次访问将 accessJWT 传给服务端。服务端校验 accessJWT 的有效性,如果过期的话,就将 refreshJWT 传给服务端。如果有效,服务端就生成新的 accessJWT 给客户端。否则,客户端就重新登录即可。

双token的不足就在于:

  • 需要客户端来配合;
  • 用户注销的时候需要同时保证两个 JWT 都无效;
  • 重新请求获取 JWT 的过程中会有短暂 JWT 不可用的情况(可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT);
  • 存在安全问题,只要拿到了未过期的 refreshJWT 就一直可以获取到 accessJWT。

原文链接:javaguide.cn/system-desi…