什么是 JWT ?JSON Web Token 入门教程

摘要:JSON Web Token(缩写 JWT)是标准化的 Token,是目前最流行的跨域认证解决方案,本文介绍它的原理和用法。JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。

JSON Web Token(缩写 JWT)是标准化的 Token,是目前最流行的跨域认证解决方案,本文介绍它的原理和用法。

JWT 是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间作为 JSON 对象,安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。

工作原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给客户端,就像下面这样。

什么是 JWT ?JSON Web Token 入门教程

{
  "姓名": "张三",
  "角色": "管理员",
  "到期时间": "2021年12月1日0点0分"
}

以后,客户端与服务端通信的时候,都要发回这个 JSON 对象,服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

服务器就不存储任何 Session 数据了,也就是说,服务器变成无状态了,从而比较容易实现横向扩展。

使用场景

身份验证

这是使用 JWT 的最常见方案。一旦用户登录,每个后续请求将包括 JWT,从而允许用户访问该令牌允许的路由、服务和资源。

单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。

单点登陆:简称为 SSO(Single Sign On),是指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

信息交换

JWT 是在各方之间安全地传输信息的一种好方法。因为可以对 JWT 进行签名(例如,使用公钥/私钥对),所以您可以确定发件人是“发件人”。

另外,由于签名是使用标头(Header)和有效载荷(Payload)计算的,因此您还可以验证内容是否遭到篡改。

数据结构

JWT 是一个很长的字符串,中间用点(.)分隔成 3 个部分:标头(Header)、有效载荷(Payload)和签名(Signature)。

什么是 JWT ?JSON Web Token 入门教程

注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。

下面依次介绍这三个部分。

Header(标头)

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg 属性表示签名的算法(algorithm),默认是 HMAC-SHA256(写成 HS256);
  • typ 属性表示这个令牌(token)的类型(type),JWT 令牌统一写为 JWT。

最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

Payload(有效载荷)

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了 7 个官方字段,供选用。

  • iss(issuer):签发者
  • exp(expiration time):过期时间
  • sub(subject):主题
  • aud(audience):受众
  • nbf(Not Before):生效时间
  • iat(Issued At):签发时间
  • jti(JWT ID):JWT 编号,用于标识该 JWT

除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

这个 JSON 对象也要使用 Base64URL 算法转成字符串。

注意,默认情况下 JWT 是未加密的(Base64 算法不是加密),所以不要把敏感信息(如:密码)放在这个部分。

Signature(签名)

Signature 部分是对前两部分的数据签名,防止数据篡改。

如果我们把所有状态信息都附加在 Token 上,服务端就可以不存储状态信息。只要服务端能确认是自己签发的 Token,而且其信息未被改动过,那就可以认为 Token 有效——“签名”可以作此保证。

平时常说的签名都存在一方签发,另一方验证的情况,所以要使用非对称加密算法。

但是在这里,签发和验证都是同一方,所以对称加密算法就能达到要求,而对称算法比非对称算法要快得多(可达数十倍差距)。

对称加密算法除了加密,还带有还原加密内容的功能,而这一功能在对 Token 签名时并无必要。

既然不需要解密,摘要(散列)算法就会更快。可以指定密钥(secret)的散列算法,自然是 HMAC。

实现过程

客户端通过 Web 表单将自己的用户名和密码,发送到服务端的接口。这一过程一般是一个 HTTP POST 请求,建议的方式是通过 SSL 加密传输(HTTPS 协议),从而避免敏感信息被嗅探。

服务端核对用户名和密码成功后,将用户的 ID、过期时间等其它非敏感信息,作为 JWT Payload(有效载荷,也称负载),将其与 Header(标头)分别进行 Base64URL 编码。

然后指定一个密钥(secret),使用 Header 里面指定的签名算法(默认是 HMAC-SHA256),按照下面的公式产生签名(Signature)。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

什么是 JWT ?JSON Web Token 入门教程

注意,密钥(secret)只能存储在服务端,不能泄露给用户,也就是加密后的内容是不可逆的。而且对于不同的加密算法其含义有所不同,一般对于 MD5 类型的摘要加密算法,secret 实际上代表的是盐值。

盐值:就是在密码 hash 过程中添加的额外的随机值,如,用户的 ID 就可以作为盐值,来进行 hash 产生密文,进而可以做到密码保护的目的。相关内容可浏览。

算出签名后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,组合成 Token,就可以返回给客户端。

什么是 JWT ?JSON Web Token 入门教程

JWTString = Base64(header).Base64(payload).HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)
  • Header 和 Payload 可以直接利用 Base64 解码出原文,从 Header 中获取哈希签名的算法,从 Payload 中获取有效数据;
  • Signature 由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验 Payload 有没有被篡改。

客户端收到服务端返回的 Token,可以储存在 Cookie,也可以储存在 localStorage 或 sessionStorage(退出登陆时即删除 Token)。

此后,客户端每次与服务端通信,都要带上这个 Token。

您可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息 Authorization 字段里,使用 Bearer 模式。

Authorization: Bearer <token>

当然,您还可以把 Token 放在 POST 请求的数据体里面,或通过 URL 传输。

http://www.dedenotes.com/user?token=xxx

服务端接收到 Token,对 Header(标头)和 Payload(有效载荷)分别进行 Base64URL 解码,检测 Token 是否过期,签发人是否是自己(可选)。

然后用 Header 中的加密算法,加上密钥(secret)对 Header、Payload 再进行一次加密,比对加密后的数据和客户端发送过来的签名(Signature)是否一致。

什么是 JWT ?JSON Web Token 入门教程

如果签名不一致,说明数据遭到篡改,服务器认证则不通过。

Base64URL

前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

标准 Base64 中使用了 '/',这在 URL 和文件系统中存在冲突,因此延伸出 Base64URL 算法,主要就是将 '+' 和 '/' 符号替换成了 '-' 和 '_' 符号,'=' 符号被省略。

JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.dedenotes.com/?token=xxx),因此需要使用 Base64URL 算法。

需要注意的是,Base64 并不是一种加密方式,明文使用 Base64 编码后的字符串,通过索引表可以直接解码为明文。因此,Base64 只能作为一种数据的存储格式。

关于 Base64 编码的更多内容请浏览。

过期时间

Token 需要设置过期时间吗?对于这个问题,我们不妨先看两个例子。

  • 登录密码:一般要求定期改变密码,以防止泄漏,所以可以理解为密码是有过期时间的。
  • 安全证书:SSL 安全证书都有过期时间,目的是为了解决吊销的问题。

所以无论是从安全的角度考虑,还是从吊销的角度考虑,Token 都需要设置过期时间。

然而新问题产生了,如果用户在正常操作的过程中,Token 过期失效了,要求用户重新登录……用户体验岂不是很糟糕?

过期失效

为了解决在操作过程中,不能让用户感知到 Token 失效这个问题,下面给出了两种方案。

  • 持久化 Token
  • 持久化 Token 是在服务端存储 Token 状态,用户每次操作都会自动刷新 Token 的过期时间(Session 就是采用这种策略来保持用户登录状态)。

    但这种方案存在这样一个问题,在前后端分离、单页 App 这些情况下,每秒种可能发起很多次请求,每次都去刷新过期时间,服务端会产生比较大的性能开销。

    如果 Token 状态被持久化到数据库或文件,性能开销就更大了。

    所以通常为了提升效率,减少消耗,会把 Token 状态存储在缓存或者内存中(如 Redis)。

  • 使用 Refresh Token
  • 使用 Refresh Token(刷新令牌)可以避免频繁的读写操作,服务端不需要刷新 Token 的过期时间,一旦 Token 过期,就反馈给客户端,客户端使用 Refresh Token 申请一个全新 Token 继续使用。

    这种方案中,服务端只需在客户端请求更新 Token 的时候,对 Refresh Token 的有效性进行一次检查,大大减少了更新过期时间的操作,也就避免了频繁读写。

    当然 Refresh Token 也是有过期时间的,但是这个有效期就可以长一点了,比如,以天为单位的时间。

    时序图

    使用 Token(Refresh Token,访问令牌)和 Refresh Token 的时序图如下:

    什么是 JWT ?JSON Web Token 入门教程

    Access Token 的有效期比较短,当 Acesss Token 由于过期而失效时,使用 Refresh Token 就可以获取到新的 Token,如果 Refresh Token 也失效了,用户就只能重新登录了。

    Refresh Token 及过期时间存储在服务端的数据库中,只有在申请新的 Acesss Token 时才会验证,不会对业务接口响应时间造成影响,不像 Session 需要应对大量的请求。

    当然还可以把这个机制设计得更复杂一些,比如,Refresh Token 每次使用的时候,都更新它的过期时间,直到与它的创建时间相比,已经超过了非常长的一段时间(比如三个月),这等于是在相当长一段时间内允许 Refresh Token 自动续期。

    在使用的时候,有两点需要注意:

    • Refresh Token 有效时间较长,所以它应该在服务器端有状态,以增强安全性,确保用户注销时可控;
    • 应该考虑使用二次认证来增强敏感操作的安全性。

    总结

    (1)JWT 默认是不加密,但也可以是加密的。生成原始 Token 以后,可以用密钥再加密一次。

    (2)JWT 不加密的情况下,不能将敏感数据写入 JWT。

    (3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

    (4)JWT 并不依赖 Cookie,所以可以使用任何域名提供 API 服务,而不需要担心跨域资源共享问题(CORS)。

    (5)JWT 最大的优势是无状态,服务器不需要存储 Session 会话信息,使得服务器认证鉴权业务可以方便扩展。

    (6)JWT 的最大缺点是,由于服务器不存储 Session 状态,因此无法在使用过程中废止某个 Token,或者更改 Token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

    (7)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该 Token 的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

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

    (9)为了避免网络劫持,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

    版权声明:本文为博主原创文章,未经博主允许不得转载。http://www.dedenotes.com/html/json-web-token.html
    (1)
    打赏 微信扫一扫 微信 支付宝 QQ 扫码打赏

    meta

    Dedenotes 赞(3)

    meta 是 html 语言 head 区的一个辅助性标签,位于文档的头部,不包含任何内容,标签的属性定义了与文档相关联的名称/值对。meta 标签可提供相关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词。

    防止表单重复提交的 4 种方法

    Dedenotes 赞(3)

    平时开发的项目中可能会出现下面这些情况:由于用户误操作,多次点击表单提交按钮;由于网速等原因造成页面卡顿,用户重复刷新提交页面;黑客或恶意用户使用 Postman 等工具重复恶意提交表单(攻击网站)。

    HTTP消息结构 HTTP请求报文和响应报文的格式

    Dedenotes 赞(3)

    HTTP 协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,基于 TCP/IP 通信协议来传递数据(HTML 文件, 图片文件, 查询结果等),所有的 WWW(World Wide Web)文件都必须遵守这个标准。