目录

JWT - JSON Web Token

JSON Web Token 是一个开放标准( RFC 7519 ),它定义了如何对JSON数据进行签名和加密。缩写是JWT。此信息可以验证和信任,因为它是数字签名的。JWT 可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

概述

在 JWT 中,可以在令牌中保存任意信息(投诉),例如,服务器可以生成一个令牌,其中包含“以管理员身份登录”的信息给客户端。客户端可以使用令牌来证明他以管理员身份登录。令牌使用一方(通常是服务器)或双方(另一方提供公钥)的私钥签名,并且可以验证颁发的令牌是否合法。

JWT 令牌具有紧凑的设计并且是URL安全的,使其易于使用,尤其是对于Web 浏览器中的单点登录(SSO)。令牌一般存储认证提供者和服务提供者认证的用户身份信息,以及每个服务所需的信息。

JWT 基于其他基于 JSON 的开放标准,JSON Web Signature ( JWS, RFC 7515 )和JSON Web Encryption ( JWE , RFC 7516 )

  • 紧凑(Compact) :由于传送的数据小,JWT 可以通过GET、POST 和 放在 HTTP 的 header 中,同时也是因为小也能传送的更快。
  • 自包含(self-contained) : Payload 中能够包含用户的信息,避免数据库的查询。

使用场景

  • 授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且能够在不同的域中轻松使用。
  • 信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。因为可以对 JWT 进行签名(例如,使用公钥/私钥对),所以您可以确定发件人就是他们所说的那个人。此外,由于使用标头和有效负载计算签名,您还可以验证内容没有被篡改。

结构

在其紧凑的形式中,JSON Web Tokens 由以点 ( .) 分隔的三部分组成,它们是:

  • Header 头部
  • Payload 负载
  • Signature 签名

因此,JWT 通常如下所示。

xxxxx.yyyyy.zzzzz

Header 头部

标头通常由两部分组成:令牌的类型,即 JWT,以及正在使用的签名算法,例如 HMAC SHA256 或 RSA。

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

然后,这个 JSON 被Base64Url编码以形成 JWT 的第一部分。

Payload 负载

令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和附加数据的陈述。索赔分为三种类型:注册索赔、公开索赔和私人索赔。

  • 注册声明:这些是一组预定义的声明,它们不是强制性的,但建议使用,以提供一组有用的、可互操作的声明。JWT 规定了7个官方字段,供选用。
    • iss (issuer):签发人
    • exp (expiration time):过期时间
    • sub (subject):主题
    • aud (audience):受众
    • nbf (Not Before):生效时间
    • iat (Issued At):签发时间
    • jti (JWT ID):编号
  • 公共声明:这些可以由使用 JWT 的人随意定义。但是为了避免冲突,它们应该在IANA JSON Web Token Registry中定义,或者定义为包含抗冲突命名空间的 URI。
  • 私人声明:这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,既不是注册声明也不是公共声明。
1
2
3
4
5
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后对有效负载进行Base64Url编码以形成 JSON Web 令牌的第二部分。

Signature 签名

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

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

例如,如果您想使用 HMAC SHA256 算法,签名将通过以下方式创建:

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

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

Base64URL

前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。 JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。

Header.Payload.Signature

输出是三个用点分隔的 Base64-URL 字符串,可以在 HTML 和 HTTP 环境中轻松传递,同时与基于 XML 的标准(如 SAML)相比更紧凑。

下面显示了一个 JWT,该 JWT 具有先前的标头和有效负载编码,并使用秘密签名。

/images/security/encoded-jwt3.png
JWT token

如果您想玩 JWT 并将这些概念付诸实践,您可以使用 jwt.io Debugger 来解码、验证和生成 JWT。

/images/security/legacy-app-auth-5.png
jwt.io Debugger

如何工作

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,每当用户想要访问受保护的路由或资源时,可以把它放在 Cookie 里面自动发送,但是这样不能跨域,更好的做法是在Authorization 标头中使用 Bearer 模式。

1
Authorization: Bearer <token>

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

请注意,如果您通过 HTTP 标头发送 JWT 令牌,则应尽量防止它们变得太大。某些服务器不接受超过 8 KB 的标头。如果您尝试在 JWT 令牌中嵌入太多信息,例如通过包含所有用户的权限,您可能需要其他解决方案,例如Auth0 Fine-Grained Authorization。

如果令牌在Authorization标头中发送,则跨域资源共享 (CORS) 不会成为问题,因为它不使用 cookie。

JWT 的几个特点

  • JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
  • JWT 不加密的情况下,不能将秘密数据写入 JWT。
  • JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
  • JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  • JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  • 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

附录