认证与权限安全
你当前正在浏览历史版本 Vert.x 4.2.7 的文档。 访问本页面的 最新版本。

JWT 身份认证与授权提供者 (vertx-auth-jwt)

该组件提供了开箱即用的 JSON Web Token(JWT)反应式安全实现。 要在项目中使用该组件,请在构建描述文件的 dependencies 区域添加以下依赖:

  • Maven(在 pom.xml 中):

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-auth-jwt</artifactId>
  <version>5.0.12</version>
</dependency>
  • Gradle(在 build.gradle 文件中):

compile 'io.vertx:vertx-auth-jwt:5.0.12'

JSON Web Token(JWT)是一种标准化的以明文形式(通常置于 HTTP 请求头或 URL 中)传输自包含信息的高效机制,其内容可以通过数字签名进行防篡改信任核验。 JWT 非常适用于以下场景:

  • 单点登录(SSO):由统一认证中心安全签发包含用户身份的令牌,业务系统以分布式方式独立完成验签。

  • 无状态 API 服务端:非常适合单页面应用(SPA)与微服务架构。

  • 等等……

在决定使用 JWT 之前,请务必注意:JWT 默认只做防篡改签名,并不加密载荷(Payload)数据。 你不应通过 JWT 传输任何敏感机密信息(如用户登录密码),而应传输不需要保密但必须核验真实性的身份数据。 例如,传递经签名的用户 ID 来标识当前登录用户是非常推荐的做法;而若在 JWT 载荷中传输用户明文密码,将带来灾难性的安全漏洞。

JWT 的主要优势包括:

  • 支持严格校验令牌的真实性与完整性。

  • 拥有灵活的 JSON 数据载荷,可容纳任意业务自定义属性字段。

  • 完全无状态,无须在服务端持久化存储 Session 会话。

创建该提供者实例需要使用 JWTAuth。 你可以通过 JSON 对象或对应 Options 类指定配置参数。

以下是创建 JWT 认证提供者的示例:

JWTAuthOptions config = new JWTAuthOptions()
  .setKeyStore(new KeyStoreOptions()
    .setPath("keystore.jceks")
    .setPassword("secret")
    .setType("jceks"));

AuthenticationProvider provider = JWTAuth.create(vertx, config);

JWT 在系统中的典型交互流程为:在你的应用中提供一个签发令牌的端点(该端点应在 SSL/TLS 安全模式下运行),在根据用户名和密码验证请求用户身份之后,执行如下操作:

JWTAuthOptions config = new JWTAuthOptions()
  .setKeyStore(new KeyStoreOptions()
    .setPath("keystore.jceks")
    .setPassword("secret")
    .setType("jceks"));

JWTAuth provider = JWTAuth.create(vertx, config);

// 在认证验证端点上,一旦通过用户名和密码核实了用户身份:
if ("paulo".equals(username) && "super_secret".equals(password)) {
  String token = provider.generateToken(
    new JsonObject().put("sub", "paulo"), new JWTOptions());

  // 后续对受保护资源的任何请求,客户端都应在 HTTP Authorization 头中传递该令牌字符串:
  // Authorization: Bearer <token>
}

加载密钥

加载密钥支持以下 3 种不同途径:

  • 使用对称密钥(共享 Secret 字符串)

  • 使用 OpenSSL pem 格式文件(公钥 / 私钥对)

  • 使用 Java Keystore 密钥库文件(支持对称密钥与公私钥对)

使用对称密钥

JWT 的默认签名方式是 HS256。HS 代表使用 SHA256 算法的 HMAC 签名。

这是最简单的一种密钥加载形式。 你所需要的仅仅是在服务端与受信任的第三方之间共享的一个 Secret 密钥。例如假设 Secret 为 keyboard cat,可以按如下方式配置:

JWTAuth provider = JWTAuth.create(vertx, new JWTAuthOptions()
  .addPubSecKey(new PubSecKeyOptions()
    .setAlgorithm("HS256")
    .setBuffer("keyboard cat")));

String token = provider.generateToken(new JsonObject());

在此场景下,Secret 配置为公钥形式(因为该密钥对通信双方均已知),并将 PubSecKey 配置为对称模式。

使用 RSA 非对称密钥

本节并不是 OpenSSL 的详尽手册,建议参阅 OpenSSL 命令行官方文档。 这里我们将介绍如何生成最常见的 RSA 密钥并在 JWT 认证中进行应用。

假设你希望使用业界极其常用的 RS256 JWT 算法来保护应用。 与一些误解相反,数字 256 指的不是密钥长度,而是哈希算法摘要的长度。 任何合法的 RSA 密钥均可配合该 JWT 算法使用。 以下为对应算法信息速查表:

"alg" 参数值 数字签名算法

RS256

使用 SHA-256 的 RSASSA-PKCS1-v1_5

RS384

使用 SHA-384 的 RSASSA-PKCS1-v1_5

RS512

使用 SHA-512 的 RSASSA-PKCS1-v1_5

如果你希望生成一个 2048 位的 RSA 密钥对,可以执行如下命令(请牢记 不要 设置密码短语 passphrase,否则 JWT 认证模块将无法读取该私钥):

openssl genrsa -out private.pem 2048

生成的密钥文件内容类似于:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxPSbCQY5mBKFDIn1kggvWb4ChjrctqD4nFnJOJk4mpuZ/u3h
...
-----END RSA PRIVATE KEY-----

由于标准 JDK 无法直接读取这种传统格式,我们 必须 先将其转换为 PKCS8 标准格式:

openssl pkcs8 -topk8 -inform PEM -in private.pem -out private_key.pem -nocrypt

现在转换后的新文件 private_key.pem 内容如下所示:

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDE9JsJBjmYEoUM
...
-----END PRIVATE KEY-----

如果仅仅是验签令牌,那么只需要提供公钥文件;但服务端通常需要签发令牌,因此同时需要公钥与私钥。 从私钥文件中导出公钥的命令如下:

openssl rsa -in private.pem -outform PEM -pubout -out public.pem

执行后导出的 public.pem 文件内容类似于:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxPSbCQY5mBKFDIn1kggv
...
qwIDAQAB
-----END PUBLIC KEY-----

现在你可以将它们加载到选项中,用于签发或校验令牌:

JWTAuth provider = JWTAuth.create(vertx, new JWTAuthOptions()
  .addPubSecKey(new PubSecKeyOptions()
    .setAlgorithm("RS256")
    .setBuffer(
      "-----BEGIN PUBLIC KEY-----\n" +
        "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxPSbCQY5mBKFDIn1kggv\n" +
        "Wb4ChjrctqD4nFnJOJk4mpuZ/u3h2ZgeKJJkJv8+5oFO6vsEwF7/TqKXp0XDp6IH\n" +
        "byaOSWdkl535rCYR5AxDSjwnuSXsSp54pvB+fEEFDPFF81GHixepIbqXCB+BnCTg\n" +
        "N65BqwNn/1Vgqv6+H3nweNlbTv8e/scEgbg6ZYcsnBBB9kYLp69FSwNWpvPmd60e\n" +
        "3DWyIo3WCUmKlQgjHL4PHLKYwwKgOHG/aNl4hN4/wqTixCAHe6KdLnehLn71x+Z0\n" +
        "SyXbWooftefpJP1wMbwlCpH3ikBzVIfHKLWT9QIOVoRgchPU3WAsZv/ePgl5i8Co\n" +
        "qwIDAQAB\n" +
        "-----END PUBLIC KEY-----"))
  .addPubSecKey(new PubSecKeyOptions()
    .setAlgorithm("RS256")
    .setBuffer(
      "-----BEGIN PRIVATE KEY-----\n" +
        "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDE9JsJBjmYEoUM\n" +
        "ifWSCC9ZvgKGOty2oPicWck4mTiam5n+7eHZmB4okmQm/z7mgU7q+wTAXv9Oopen\n" +
        "RcOrogdvJo5JZ2SXnImsJhHkDENKPCe5JewKnnim8H58QQUQ8UXzUYeLF6khupcI\n" +
        "H4GcJOFzrkGrA2f/VWCq/r4fefB42VtO/x7+xwSBuDplhyycEEH2Rgunr0VLA1am\n" +
        "8+Z3rR7cNbIijdYJSYqVCCMcvg8cstjDAqA4cb9o2XiE3j/CpOLEIAd7op0ud6Eu\n" +
        "fvXH5nRLJdtaih+15+kk/XAxvCUKkfeKQHNUh8cptZP1Ag5WhGByE9TdYCxm/94+\n" +
        "CXmLwKirAgMBAAECggEBAJ2IuX/e112s2tcun5tW6Eu5a59AxhwV6KGn/k+3t1wQ\n" +
        "L787pu9lR5sV78sXvUvT0p6mZ8u/L8k5l3mZ9u/L8k5l3mZ9u/L8k5l3mZ9u/L8k\n" +
        "suN/vRelqbwhufAs85XBBY99vWtxdpsVSt5nx2YvegCgdIj/jUAU2B7hGQKBgEgV\n" +
        "sCRdaJYr35FiSTsEZMvUZp5GKFka4xzIp8vxq/pIHUXp0FEz3MRYbdnIwBfhssPH\n" +
        "/yKzdUxcOLlBtry+jgo0nyn26/+1Uyh5n3VgtBBSePJyW5JQAFcnhqBCMlOVk5pl\n" +
        "/7igiQYux486PNBLv4QByK0gV0SPejDzeqzIyB+xAoGAe5if7DAAKhH0r2M8vTkm\n" +
        "JvbCFjwuvhjuI+A8AuS8zw634BHne2a1Fkvc8c3d9VDbqsHCtv2tVkxkKXPjVvtB\n" +
        "DtzuwUbp6ebF+jOfPK0LDuJoTdTdiNjIcXJ7iTTI3cXUnUNWWphYnFogzPFq9CyL\n" +
        "0fPinYmDJpkwMYHqQaLGQyg=\n" +
        "-----END PRIVATE KEY-----")
  ));

String token = provider.generateToken(
  new JsonObject().put("some", "token-data"),
  new JWTOptions().setAlgorithm("RS256"));

使用椭圆曲线(EC)密钥

模块同样支持椭圆曲线(Elliptic Curve)密钥,不过默认 JDK 在可用特性上存在一定限制。

其使用流程与 RSA 非常相似,首先生成私钥:

openssl ecparam -name secp256r1 -genkey -out private.pem

生成的内容如下所示:

-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIdV497z2F/hC4V4oXwH9n7P+X/e112s2tcun5tW6Eu5oAoGCCqGSM49
...
-----END EC PRIVATE KEY-----

但由于 JDK 偏好 PKCS8 格式,必须先执行转换:

openssl pkcs8 -topk8 -nocrypt -in private.pem -out private_key.pem

转换后将获得符合 PKCS8 规范的私钥:

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgeRyEfU1NSHPTCuC9
...
-----END PRIVATE KEY-----

利用该私钥即可直接签发令牌:

JWTAuth provider = JWTAuth.create(vertx, new JWTAuthOptions()
  .addPubSecKey(new PubSecKeyOptions()
    .setAlgorithm("ES256")
    .setBuffer(
      "-----BEGIN PRIVATE KEY-----\n" +
        "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgeRyEfU1NSHPTCuC9\n" +
        "rwLZMukaWCH2Fk6q5w+XBYrKtLihRANCAAStpUnwKmSvBM9EI+W5QN3ALpvz6bh0\n" +
        "SPCXyz5KfQZQuSj4f3l+xNERDUDaygIUdLjBXf/bc15ur2iZjcq4r0Mr\n")
  ));

String token = provider.generateToken(
  new JsonObject(),
  new JWTOptions().setAlgorithm("ES256"));

为了校验生成的令牌,需要导出对应的公钥:

openssl ec -in private.pem -pubout -out public.pem

然后配置公钥以支持全套操作:

JWTAuth provider = JWTAuth.create(vertx, new JWTAuthOptions()
  .addPubSecKey(new PubSecKeyOptions()
    .setAlgorithm("ES256")
    .setBuffer(
      "-----BEGIN PUBLIC KEY-----\n" +
        "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEraVJ8CpkrwTPRCPluUDdwC6b8+m4\n" +
        "dEjwl8s+Sn0GULko+H95fsTREQ1A2soCFHS4wV3/23Nebq9omY3KuK9DKw==\n" +
        "-----END PUBLIC KEY-----"))
  .addPubSecKey(new PubSecKeyOptions()
    .setAlgorithm("ES256")
    .setBuffer(
      "-----BEGIN PRIVATE KEY-----\n" +
        "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgeRyEfU1NSHPTCuC9\n" +
        "rwLZMukaWCH2Fk6q5w+XBYrKtLihRANCAAStpUnwKmSvBM9EI+W5QN3ALpvz6bh0\n" +
        "SPCXyz5KfQZQuSj4f3l+xNERDUDaygIUdLjBXf/bc15ur2iZjcq4r0Mr")
  ));

String token = provider.generateToken(
  new JsonObject(),
  new JWTOptions().setAlgorithm("ES256"));

JWT 密钥库(Keystore)文件

如果你更偏好使用 Java Keystore,该提供者也提供了原生支持。

该认证提供者要求类路径(Classpath)或文件系统中存在一个密钥库文件,并通过 javax.crypto.Mac 或 java.security.Signature 对生成的令牌进行签名与验签。

默认情况下,实现类将检索以下别名(Alias),但并不要求全部存在。作为最佳实践,通常应至少提供 HS256:

`HS256`:: 使用 SHA-256 哈希算法的 HMAC
`HS384`:: 使用 SHA-384 哈希算法的 HMAC
`HS512`:: 使用 SHA-512 哈希算法的 HMAC
`RS256`:: 使用 SHA-256 哈希算法的 RSASSA
`RS384`:: 使用 SHA-384 哈希算法的 RSASSA
`RS512`:: 使用 SHA-512 哈希算法的 RSASSA
`ES256`:: 使用 P-256 曲线与 SHA-256 哈希算法的 ECDSA
`ES384`:: 使用 P-384 曲线与 SHA-384 哈希算法的 ECDSA
`ES512`:: 使用 P-521 曲线与 SHA-512 哈希算法的 ECDSA

当未提供密钥库时,实现将降级到非安全模式,不对签名进行校验。这适用于载荷数据已通过外部手段完成签名或加密的特殊场景。

存储在 Keystore 中的密钥对始终包含证书。证书的有效性在加载时会被检验;若证书已过期或尚未生效,密钥将拒绝加载。

所有密钥算法均会校验是否与给定的别名匹配。例如,如果声明了 RS256 别名但实际颁发的是 EC 算法,或者虽为 RSA 但签名为 SHA1WithRSA 而非 SHA256WithRSA,均会加载失败。

生成新的 Keystore 文件

生成 Keystore 文件唯一需要的工具是 JDK 自带的 keytool。你可以通过运行以下命令来创建所需算法的密钥库:

keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg HMacSHA256 -keysize 2048 -alias HS256 -keypass secret
keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg HMacSHA384 -keysize 2048 -alias HS384 -keypass secret
keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg HMacSHA512 -keysize 2048 -alias HS512 -keypass secret
keytool -genkey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg RSA -keysize 2048 -alias RS256 -keypass secret -sigalg SHA256withRSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
keytool -genkey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg RSA -keysize 2048 -alias RS384 -keypass secret -sigalg SHA384withRSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
keytool -genkey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg RSA -keysize 2048 -alias RS512 -keypass secret -sigalg SHA512withRSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
keytool -genkeypair -keystore keystore.jceks -storetype jceks -storepass secret -keyalg EC -keysize 256 -alias ES256 -keypass secret -sigalg SHA256withECDSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
keytool -genkeypair -keystore keystore.jceks -storetype jceks -storepass secret -keyalg EC -keysize 384 -alias ES384 -keypass secret -sigalg SHA384withECDSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
keytool -genkeypair -keystore keystore.jceks -storetype jceks -storepass secret -keyalg EC -keysize 521 -alias ES512 -keypass secret -sigalg SHA512withECDSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360

有关密钥库以及如何使用 PKCS12 格式(Java 9+ 默认推荐格式)的更多信息,请参阅 common 模块的文档。

只读令牌消费

如果你只需要消费第三方系统签发的 JWT 令牌,你通常不需要持有私钥,此时只需准备 PEM 格式的公钥即可:

JWTAuthOptions config = new JWTAuthOptions()
  .addPubSecKey(new PubSecKeyOptions()
    .setAlgorithm("RS256")
    .setBuffer("BASE64-ENCODED-PUBLIC_KEY"));

AuthenticationProvider provider = JWTAuth.create(vertx, config);

基于 JWT 的认证与授权 (AuthN/AuthZ)

在微服务架构等开发场景中,常见需求是让你的应用调用内部或第三方 API。 这些 API 通常面向程序而非人类交互,因此需要消除所有交互式认证环节。

在此场景下,可以采用 HTTP 协议消费该 API,而 HTTP 规范已经明确定义了通过 Authorization 请求头传递授权凭据的标准。 在绝大多数情况下,令牌以 Bearer 方式传递,即:Authorization: Bearer <base64编码字符串>。

身份认证 (AuthN)

对于本提供者而言,当且仅当令牌通过签名有效性检查且尚未过期时,该用户才被视为认证成功。 因此,私钥必须严格保密,绝不能在不同工程间随意复制粘贴,以免造成重大安全漏洞。

jwtAuth.authenticate(new TokenCredentials("BASE64-ENCODED-STRING"))
  .onSuccess(user -> System.out.println("用户:" + user.principal()))
  .onFailure(err -> {
    // 认证失败!
  });

简而言之,提供者会对以下内容进行全面校验:

  • 令牌签名与内部密钥校验一致

  • 声明字段:exp(过期时间)、iat(签发时间)、nbf(生效时间)、audience(受众)、issuer(签发者)均符合预设配置

如果所有条件均满足,则认为令牌合法并返回对应的用户对象。

虽然 exp、iat 和 nbf 均为时间戳检查,但只有 exp 支持通过配置予以忽略:

JWTAuth jwtAuth = JWTAuth
  .create(vertx, new JWTAuthOptions()
    .setJWTOptions(new JWTOptions().setIgnoreExpiration(true)));

// 该字符串即为 HTTP Authorization 头中紧随 "Bearer " 之后的 Token 串

// 在该示例中,我们强制提供者忽略 `exp` 过期字段检查:
jwtAuth.authenticate(
    new TokenCredentials("BASE64-ENCODED-STRING"))
  .onSuccess(user -> System.out.println("用户:" + user.principal()))
  .onFailure(err -> {
    // 认证失败!
  });

若需校验 aud(受众)字段,可以类似地传入选项:

JWTAuth jwtAuth = JWTAuth
  .create(vertx, new JWTAuthOptions()
    .setJWTOptions(new JWTOptions().addAudience("my-service")));

// 该字符串即为 HTTP Authorization 头中紧随 "Bearer " 之后的 Token 串

// 在该示例中,我们强制提供者校验 aud 字段:
jwtAuth.authenticate(
    new TokenCredentials("BASE64-ENCODED-STRING"))
  .onSuccess(user -> System.out.println("用户:" + user.principal()))
  .onFailure(err -> {
    // 认证失败!
  });

校验签发者(Issuer)亦同理:

JWTAuth jwtAuth = JWTAuth
  .create(vertx, new JWTAuthOptions()
    .setJWTOptions(new JWTOptions().setIssuer("my-corp.com")));

// 该字符串即为 HTTP Authorization 头中紧随 "Bearer " 之后的 Token 串

// 在该示例中,我们强制提供者校验 issuer 字段:
jwtAuth.authenticate(
    new TokenCredentials("BASE64-ENCODED-STRING"))
  .onSuccess(user -> System.out.println("用户:" + user.principal()))
  .onFailure(err -> {
    // 认证失败!
  });

权限授权 (AuthZ)

一旦令牌解析成功且校验合法,我们即可使用它来执行授权任务。 最简单的操作是核实用户是否拥有特定权限。 授权流程遵循通用的 AuthorizationProvider API。 选择签发当前令牌的对应提供者并进行评估。

目前提供两种工厂实现:

  • JWTAuthorization:基于 "permissions" 声明键来检查令牌权限。

  • MicroProfileAuthorization:基于 <a href="https://www.eclipse.org/community/eclipse_newsletter/2017/september/article2.php">MP JWT 规范</a> 来检查令牌权限。

典型用法是通过提供者从用户对象中提取权限集合并执行断言校验:

AuthorizationProvider authz = MicroProfileAuthorization.create();

authz.getAuthorizations(user)
  .onSuccess(v -> {
    // 随后根据业务需要执行匹配检查:
    if (PermissionBasedAuthorization.create("create-report").match(user)) {
      // 匹配成功,该用户拥有创建报表权限
    }
  });

默认情况下,提供者在 permissions 键名下进行查找;但与其他提供者一致,可以通过 : 作为分隔符将权限概念扩展为角色,例如使用 role:authority 来检索令牌权限。

由于 JWT 载荷结构非常自由,业界对于声明项存放在何处并无强制标准,因此可以自由配置声明键名而不仅限于 permissions。例如可以指定深层 JSON 路径:

JsonObject config = new JsonObject()
  .put("public-key", "BASE64-ENCODED-PUBLIC_KEY")
  // 消费 Keycloak 签发的 JWT 时,需要将权限声明映射到令牌内的深层路径:
  .put("permissionsClaimKey", "realm_access/roles");

AuthenticationProvider provider =
  JWTAuth.create(vertx, new JWTAuthOptions(config));

在上述示例中,我们配置了 JWT 以适配 Keycloak 令牌格式。 此时权限声明将从 realm_access/roles 路径下读取,而非默认的 permissions。

验证令牌

当调用 authenticate 方法时,令牌将基于初始化阶段配置的 JWTOptions 进行校验。 验证过程执行以下步骤:

  1. 如果 ignoreExpiration(默认 false)为 false,则检查令牌是否过期。该过程会依次检验 exp、iat 与 nbf 字段。 考虑到服务器之间可能存在时钟漂移,可以配置一定的 leeway(时间容差),在时间略微超出限制时提供宽限期。

  2. 如果配置了 audience,则核对令牌中的 aud 是否与之一致,且所有预设的 audience 必须均存在于令牌中。

  3. 如果配置了 issuer,则核对令牌中的 iss 是否与预设的签发者一致。

上述所有验证均通过后,将返回一个 JWTUser 对象。该对象内部引用了配置中指定的权限声明键,在后续进行授权操作时使用。该值指向应检查权限凭证的 JSON 路径。

自定义令牌生成

与令牌验证类似,令牌生成行为在初始化配置中预设。

在生成令牌时,可以传入一个可选的 JWTOptions 参数来精确控制生成细节: 可以使用属性 algorithm 配置令牌签名算法(默认为 HS256),此时系统会查找与该算法对应的密钥进行签名。

若需要向默认标头合并附加信息,可以通过选项中的 headers 属性传入自定义 Header。

有时在测试或本地开发阶段,签发不带时间戳的令牌非常方便;此时可以将属性 noTimestamp 设置为 true(默认为 false),此时生成的令牌中将不包含 iat 字段。

令牌的有效期通过 expiresInSeconds 属性控制,默认不设置过期时间。 配置中的其他控制字段如 audience、issuer 与 subject 只要存在,均会被自动提取并写入令牌元数据中。

最后,令牌被计算签名并编码为规范的标准字符串格式。