上一篇 《分布式 Session 常见的几种解决方案》中提到了一种方案,就是不使用 Session,保持服务的无状态化,使用 Json Web Token 来代替 Session 实现数据共享。

那么这种方案是否可行呢?

1. JWT 详解

1.1 JWT 是什么

JWT 一看就是简称,它的全称 JSON Web Token,从字面上我们看出 :::info

  • 数据是 JSON 格式
  • 用于 Web 应用
  • 是一个 Token,也就是一个令牌方式 :::

看看官方的说明,它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象进行安全传输信息。这些信息可以通过对称/非对称方式进行签名,防止信息被串改。 :::info

  • 紧凑的含义:就是 JWT 比较小,数据量不大,可以通过 URL、POST 参数或 Header 请求头方式进行传输。
  • 自包含的含义:jwt 可以让用户自定义JWT里面包含的用户信息,如:姓名、昵称等(不要放隐密的信息)。从而避免了多次查询数据库。 :::

1.2 JWT 数据结构

JWT由三个部分组成: :::info

  • Header
  • Payload
  • Signature :::

三者组合在一起:Header . Payload . Signature

案例:
不推荐 JWT 实现分布式 Session - 图1

(1) Header

这个是 JWT 第一段数据,表示头部信息,主要的作用是描述 JWT 的元数据,上面的案例就是:

  1. {
  2. alg: "HS256",
  3. typ: "JWT"
  4. }

:::info

  • alg 属性表示签名的算法,默认算法为 HS256,可以自行别的算法。
  • typ 属性表示这个令牌的类型,JWT 令牌就为 JWT。 :::

上面的 JSON 数据会通过 Base64 算法进行编码而成,看工具图:
image.png

(2) Payload

此为 JWT 第二段数据,用来存放实际需要传递的数据。JWT 官方也规定了7个字段供选用:
不推荐 JWT 实现分布式 Session - 图3
当然除了官方字段,我们可以自定义字段,以上面的案例,我们看下实际的数据:
不推荐 JWT 实现分布式 Session - 图4

:::info 注意:这段也是用 Base64 算法,JWT 默认是不加密的,任何人都可以获取,只要进行 Base64 解码就行了,所以不要把隐密的信息放到 JWT 中 :::

(3) Signature

此为 JWT 第三段数据,主要作用是对前面两段的数据进行签名,防止数据篡改。一般我们进行签名的时候会有个密钥(secret)只有服务器知道,然后利用 Header 中的签名算法进行签名,公式如下:

  1. HMACSHA256(
  2. base64UrlEncode(header) + "." + base64UrlEncode(payload),
  3. secret)

算出签名后,把 Header、Payload、Signature 三个部分拼成一个字符串,之间用(.)分隔,这样就可以把组合而成的字符串返回给用户了。

1.3 JWT 工作方式

在用户进行认证登录时,登录成功后服务器会返回一个 JWT 给客户端;那这个 JWT 就是用户的凭证,以后到哪里去都要带上这个凭证 token。尤其访问受保护的资源的时候,通常把 JWT 放在 Authorization header 中。要用 Bearer schema,如 header 请求头中:

  1. Authorization: Bearer <token>

不推荐 JWT 实现分布式 Session - 图5

1.4 基于 JWT 的身份认证

上面的 JWT 的工作方式,其实就是一个完整的身份认证流程,我们这里把这个讲的在通俗一点: :::info 1、用户提供用户名和密码登录
2、服务器校验用户是否正确,如正确,就返回 token 给客户端,此 token 可以包含用户信息
3、客户端存储 token,可以保存在 cookie 或者 local storage
4、客户端以后请求时,都要带上这个 token,一般放在请求头中
5、服务器判断是否存在 token,并且解码后就可以知道是哪个用户
6、服务器这样就可以返回该用户的相关信息了 :::

这个流程小伙伴们有没有发现,用户信息是放在 JWT 中的是存放在客户端(cookie,local storage)中的,服务器只需解码验证就行了,就可以知道获取到用户信息。而我们之前的 Session 方式就不一样。

1.5 与 Session-Cookie 方式的区别

Session-Cookie 方式的这里就不多作介绍了,之前文章已经介绍了。直接上图说明区别:
不推荐 JWT 实现分布式 Session - 图6
上图是 Sesson 服务器方式,我们发现 Session 用户信息是在服务器端存储的。我们再来看看 JWT 方式:
不推荐 JWT 实现分布式 Session - 图7
上面的 token 即用户信息是存储在客户端的,服务器端只要解码即可。

1.6 JWT 方式认证的好处

1、因为 token 存储在客户端,服务器只负责解码。这样不需要占用服务器端资源
2、服务器端可以无限扩展,负载均衡器可以将用户传递到任何服务器,服务器都能知道用户信息,因为 jwt 里面包含了。
3、数据安全,因为有签名,防止了篡改,但信息还是透明的,不要放敏感信息。
4、放入请求头提交,很好的防止了 csrf 攻击

1.7 JWT方式认证的坏处

  1. token 失效问题

JWT 方式最大的坏处就是无法主动让 token 失效,token 本身是有过期时间,但 token 一旦发出,服务器就无法收回。 :::info 如:一个 jwt 的 token 的失效时间是3天,但我们发现这个 token 有异常,有可能被人登录,那真实的用户可以修改密码。但是即使修改了密码,那个异常的 token 还是合法的,因为3天的失效时间未到,我们服务器是没法主动让异常 token 失效。 :::

  1. 数据延时,不一致问题

jwt 中包含了用户的部分信息,如果这些部分信息修改了,服务器获取的还是以前的 jwt 中的用户信息,导致数据不一致。

1.8 JWT 使用场景和优劣

不推荐 JWT 实现分布式 Session - 图8

(1) 一次性验证

例如,用户注册后需要发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几小时之内激活),不能被篡改以激活其他可能的账户。

这种场景就和 jwt 的特性非常贴近,jwt 的 payload 中固定的参数:iss 签发者和 exp 过期时间正是为其做准备的。

(2) Restful API 的无状态认证

使用 JWT 来做 Restful API 的身份认证也是值得推崇的一种使用方案。

客户端和服务端共享 secret;过期时间由服务端校验,客户端定时刷新;签名信息不可被修改。

Spring Security Oauth JWT 提供了一套完整的 JWT 认证体系,以笔者的经验来看:使用 Oauth2 或 JWT 来做 Restful API 的认证都没有大问题,Oauth2 功能更多,支持的场景更丰富,后者实现简单。

(3) 使用 JWT 做单点登录+Session管理(不推荐)

首先明确一点:使用 jwt 来设计单点登录系统是一个不太严谨的说法。首先 cookie+jwt 的方案前提是非跨域的单点登录(cookie 无法被自动携带至其他域名)。其次单点登录系统包含了很多技术细节,至少包含了身份认证和会话管理,这还不涉及到权限管理。如果觉得比较抽象,不妨用传统的 session+cookie 单点登录方案来做类比,通常我们可以选择 spring security(身份认证和权限管理的安全框架)和 spring session(session 共享)来构建,而选择用 jwt 设计单点登录系统需要解决很多传统方案中同样存在和本不存在的问题,以下不一一详细罗列,详细内容,参考文章:https://baijiahao.baidu.com/s?id=1598976581711450442&wfr=spider&for=pc

  • JWT token 泄露了怎么办?
  • secret 如何设计?
  • 注销和修改密码
  • 续签问题

1.9 总结

1、在 Web 应用中,别再把 JWT 当做 session 使用,绝大多数情况下,传统的 cookie-session 机制工作得更好


2、JWT 适合一次性的命令认证,颁发一个有效期极短的 JWT,即使暴露了危险也很小,由于每次操作都会生成新的 JWT,因此也没必要保存 JWT,真正实现无状态。

参考: https://mp.weixin.qq.com/s/XYjOXSziF5AGK2-kmmVVzA https://mp.weixin.qq.com/s/phPQZ3gxxG9J2OQs9KXlrQ https://baijiahao.baidu.com/s?id=1598976581711450442&wfr=spider&for=pc