认证与授权

JWT 身份验证提供程序

此组件包含一个开箱即用的 JWT 实现。要使用此项目,请将以下依赖项添加到构建描述符的 dependencies 部分

  • Maven(在您的 pom.xml 中)

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

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

JSON Web Token 是一种以明文(通常在 URL 中)发送信息并可验证其内容可信度的简单方式。JWT 非常适合以下场景:

  • 在单点登录(Single Sign-On)场景中,您需要一个独立的身份验证服务器,该服务器可以以可信方式发送用户信息。

  • 无状态 API 服务器,非常适合单页应用。

  • 等等……

在决定使用 JWT 之前,请务必注意 JWT 不会加密有效负载,它只对其进行签名。您不应使用 JWT 发送任何秘密信息,而应发送非秘密但需要验证的信息。例如,发送一个已签名的用户 ID 来指示应登录的用户会非常有效!发送用户密码则会非常、非常糟糕。

其主要优点是

  • 它允许您验证令牌的真实性。

  • 它有一个 JSON 主体,可以包含您想要的任意数量的数据。

  • 它完全无状态。

要创建提供程序实例,您可以使用 JWTAuth。您在 JSON 对象中指定配置。

以下是创建 JWT 身份验证提供程序的一个示例

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

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

JWT 的典型使用流程是,在您的应用程序中有一个端点用于颁发令牌,此端点应在 SSL 模式下运行,之后您验证请求用户,例如通过其用户名和密码,您可以执行以下操作

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

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

// on the verify endpoint once you verify the identity
// of the user by its username/password
if ("paulo".equals(username) && "super_secret".equals(password)) {
  String token = provider.generateToken(
    new JsonObject().put("sub", "paulo"), new JWTOptions());

  // now for any request to protected resources you should
  // pass this string in the HTTP header Authorization as:
  // Authorization: Bearer <token>
}

加载密钥

加载密钥可以通过 3 种不同的方式进行

  • 使用密钥(对称密钥)

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

  • 使用 Java 密钥库文件(对称密钥和公钥/私钥)

使用对称密钥

JWT 的默认签名方法称为 HS256。在此情况下,HS 代表 使用 SHA256 的 HMAC 签名

这是最简单的加载密钥方式。您只需要一个在您和第三方之间共享的密钥,例如,假设密钥是:keyboard cat,那么您可以将身份验证配置为

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

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

在这种情况下,密钥被配置为公钥,因为它是一个双方都已知的令牌,并且您将您的公钥/私钥配置为对称密钥。

使用 RSA 密钥

本节绝非 OpenSSL 手册,建议阅读 OpenSSL 命令行用法。我们将介绍如何生成最常见的密钥以及如何在 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 密钥对,那么您可以这样做(请记住不要添加密码,否则您将无法在 JWT 身份验证中读取私钥)

openssl genrsa -out private.pem 2048

您可以观察到密钥是正确的,因为文件内容与此类似

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxPSbCQY5mBKFDIn1kggvWb4ChjrctqD4nFnJOJk4mpuZ/u3h
...
e4k0yN3F1J1DVlqYWJxaIMzxavQsi9Hz4p2JgyaZMDGB6kGixkMo
-----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-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDE9JsJBjmYEoUM
...
0fPinYmDJpkwMYHqQaLGQyg=
-----END PRIVATE KEY-----

如果我们只验证令牌(您只需要 private_key.pem 文件),但在某些时候您也需要颁发令牌,因此您需要一个公钥。在这种情况下,您需要从私钥文件中提取公钥

openssl rsa -in private.pem -outform PEM -pubout -out 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" +
        "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDE9JsJBjmYEoUM\n" +
        "ifWSCC9ZvgKGOty2oPicWck4mTiam5n+7eHZmB4okmQm/z7mgU7q+wTAXv9Oopen\n" +
        "RcOnogdvJo5JZ2SXnfmsJhHkDENKPCe5JexKnnim8H58QQUM8UXzUYeLF6khupcI\n" +
        "H4GcJOA3rkGrA2f/VWCq/r4fefB42VtO/x7+xwSBuDplhyycEEH2Rgunr0VLA1am\n" +
        "8+Z3rR7cNbIijdYJSYqVCCMcvg8cspjDAqA4cb9o2XiE3j/CpOLEIAd7op0ud6Eu\n" +
        "fvXH5nRLJdtaih+15+kk/XAxvCUKkfeKQHNUh8cotZP1Ag5WhGByE9TdYCxm/94+\n" +
        "CXmLwKirAgMBAAECggEAeQ+M+BgOcK35gAKQoklLqZLEhHNL1SnOhnQd3h84DrhU\n" +
        "CMF5UEFTUEbjLqE3rYGP25mdiw0ZSuFf7B5SrAhJH4YIcZAO4a7ll23zE0SCW+/r\n" +
        "zr9DpX4Q1TP/2yowC4uGHpBfixxpBmVljkWnai20cCU5Ef/O/cAh4hkhDcHrEKwb\n" +
        "m9nymKQt06YnvpCMKoHDdqzfB3eByoAKuGxo/sbi5LDpWalCabcg7w+WKIEU1PHb\n" +
        "Qi+RiDf3TzbQ6TYhAEH2rKM9JHbp02TO/r3QOoqHMITW6FKYvfiVFN+voS5zzAO3\n" +
        "c5X4I+ICNzm+mnt8wElV1B6nO2hFg2PE9uVnlgB2GQKBgQD8xkjNhERaT7f78gBl\n" +
        "ch15DRDH0m1rz84PKRznoPrSEY/HlWddlGkn0sTnbVYKXVTvNytKSmznRZ7fSTJB\n" +
        "2IhQV7+I0jeb7pyLllF5PdSQqKTk6oCeL8h8eDPN7awZ731zff1AGgJ3DJXlRTh/\n" +
        "O6zj9nI8llvGzP30274I2/+cdwKBgQDHd/twbiHZZTDexYewP0ufQDtZP1Nk54fj\n" +
        "EpkEuoTdEPymRoq7xo+Lqj5ewhAtVKQuz6aH4BeEtSCHhxy8OFLDBdoGCEd/WBpD\n" +
        "f+82sfmGk+FxLyYkLxHCxsZdOb93zkUXPCoCrvNRaUFO1qq5Dk8eftGCdC3iETHE\n" +
        "6h5avxHGbQKBgQCLHQVMNhL4MQ9slU8qhZc627n0fxbBUuhw54uE3s+rdQbQLKVq\n" +
        "lxcYV6MOStojciIgVRh6FmPBFEvPTxVdr7G1pdU/k5IPO07kc6H7O9AUnPvDEFwg\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 密钥

也支持椭圆曲线密钥,但默认的 JDK 对可使用的功能有一些限制。

用法与 RSA 非常相似,首先您创建一个私钥

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

所以您会得到类似这样的内容

-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMZGaqZDTHL+IzFYEWLIYITXpGzOJuiQxR2VNGheq7ShoAoGCCqGSM49
AwEHoUQDQgAEG1O9LCrP6hg3Y9q68+LF0q48UcOkwVKE1ax0b56wjVusf3qnuFO2
/+XHKKhtzEavvFMeXRQ+ZVEqM0yGNb04qw==
-----END EC PRIVATE KEY-----

然而 JDK 更偏好 PKCS8 格式,所以我们必须进行转换

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

这将为您提供一个类似这样的密钥

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxkZqpkNMcv4jMVgR
YshghNekbM4m6JDFHZU0aF6rtKGhRANCAAQbU70sKs/qGDdj2rrz4sXSrjxRw6TB
UoTVrHRvnrCNW6x/eqe4U7b/5ccoqG3MRq+8Ux5dFD5lUSozTIY1vTir
-----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" +
        "-----END PRIVATE KEY-----\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 密钥库文件

如果您更喜欢使用 Java 密钥库,那么也可以这样做。

此身份验证提供程序需要在类路径或文件系统中有一个密钥库,其中包含 javax.crypto.Macjava.security.Signature,以便对生成的令牌进行签名和验证。

默认情况下,实现将查找以下别名,但并非所有别名都必须存在。作为一项良好实践,HS256 应该存在。

`HS256`:: HMAC using SHA-256 hash algorithm
`HS384`:: HMAC using SHA-384 hash algorithm
`HS512`:: HMAC using SHA-512 hash algorithm
`RS256`:: RSASSA using SHA-256 hash algorithm
`RS384`:: RSASSA using SHA-384 hash algorithm
`RS512`:: RSASSA using SHA-512 hash algorithm
`ES256`:: ECDSA using P-256 curve and SHA-256 hash algorithm
`ES384`:: ECDSA using P-384 curve and SHA-384 hash algorithm
`ES512`:: ECDSA using P-521 curve and SHA-512 hash algorithm

如果未提供密钥库,则实现将回退到不安全模式,并且不会验证签名,这对于有效负载已通过外部方式签名和/或加密的情况很有用。

存储在密钥库中的密钥对总是包含一个证书。证书的有效性在加载时进行测试,如果密钥已过期或尚未生效,则不会加载。

所有密钥算法都会检查是否与给定别名匹配。例如,如果 RS256 密钥是使用 EC 算法颁发的,或者使用 RSA 但签名是 SHA1WithRSA 而不是 SHA256WithRSA,则不会加载该密钥。

生成新的密钥库文件

生成密钥库文件唯一需要的工具是 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 默认)的更多信息,请参阅通用模块的文档。

只读令牌

如果您需要使用第三方颁发的 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 tokens)的形式发送,即:Authorization: Bearer some+base64+string

身份验证 (AuthN)

对于此提供程序,如果令牌通过签名检查并且未过期,则用户通过身份验证。因此,私钥必须妥善保管,不得在项目之间复制粘贴,否则将存在安全漏洞。

jwtAuth.authenticate(new TokenCredentials("BASE64-ENCODED-STRING"))
  .onSuccess(user -> System.out.println("User: " + user.principal()))
  .onFailure(err -> {
    // Failed!
  });

简而言之,提供程序会检查以下几项

  • 令牌签名对内部私钥有效

  • 字段:expiatnbfaudienceissuer 根据配置有效

如果所有这些都有效,则令牌被认为是有效的,并返回一个用户对象。

虽然 expiatnbf 字段只是简单的时间戳检查,但只有 exp 可以配置为被忽略

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

// This string is what you see after the string "Bearer" in the
// HTTP Authorization header

// In this case we are forcing the provider to ignore the `exp` field
jwtAuth.authenticate(
    new TokenCredentials("BASE64-ENCODED-STRING"))
  .onSuccess(user -> System.out.println("User: " + user.principal()))
  .onFailure(err -> {
    // Failed!
  });

为了验证 aud 字段,需要像之前一样传递选项

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

// This string is what you see after the string "Bearer" in the
// HTTP Authorization header

// In this case we are forcing the provider to verify the aud field
jwtAuth.authenticate(
    new TokenCredentials("BASE64-ENCODED-STRING"))
  .onSuccess(user -> System.out.println("User: " + user.principal()))
  .onFailure(err -> {
    // Failed!
  });

发布者(issuer)也是如此

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

// This string is what you see after the string "Bearer" in the
// HTTP Authorization header

// In this case we are forcing the provider to verify the issuer
jwtAuth.authenticate(
    new TokenCredentials("BASE64-ENCODED-STRING"))
  .onSuccess(user -> System.out.println("User: " + user.principal()))
  .onFailure(err -> {
    // Failed!
  });

授权 (AuthZ)

一旦令牌被解析并有效,我们就可以使用它来执行授权任务。最简单的方法是验证用户是否具有特定权限。授权将遵循通用的 AuthorizationProvider API。选择生成您的令牌的提供程序并进行评估。

目前有 2 个工厂

典型的用法是使用提供程序从用户对象中提取权限并执行认证

AuthorizationProvider authz = MicroProfileAuthorization.create();

authz.getAuthorizations(user)
  .onSuccess(v -> {
    // and now we can perform checks as needed
    if (PermissionBasedAuthorization.create("create-report").match(user)) {
      // Yes the user can create reports
    }
  });

默认情况下,提供程序将在键 permissions 下查找,但与其他提供程序一样,可以使用 : 作为分隔符将概念扩展到权限和角色,因此 role:authority 可用于查找令牌。

由于 JWT 形式自由,并且没有关于在哪里查找声明的标准,因此可以将位置配置为使用 permissions 之外的其他内容,例如,甚至可以在这样的路径下查找

JsonObject config = new JsonObject()
  .put("public-key", "BASE64-ENCODED-PUBLIC_KEY")
  // since we're consuming keycloak JWTs we need
  // to locate the permission claims in the token
  .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,则检查令牌是否过期,这将检查字段:expiatnbf。由于有时时钟不可靠,可以配置一些 leeway(宽限时间)应用于日期,这样如果日期超出所需限制,我们允许一定的宽限期。

  2. 如果提供了 audience,则令牌的 aud 会与配置的进行检查,并且所有配置的受众都必须在令牌中。

  3. 如果配置了 issuer,则令牌的 iss 会与配置的进行检查。

一旦这些验证完成,就会返回一个 JWTUser 对象,该对象配置有对配置中提供的权限声明键的引用。此值稍后在执行授权时使用。该值对应于应检查权限的 JSON 路径。

自定义令牌生成

与令牌验证方式相同,令牌生成也在初始化期间进行初步配置。

生成令牌时,可以提供一个可选的额外参数来控制令牌生成,这是一个 JWTOptions 对象。令牌签名算法(默认为 HS256)可以使用 algorithm 属性进行配置。在这种情况下,会查找与该算法对应的密钥并用于签名。

可以通过使用选项 headers 属性指定任何要与默认头部合并的额外头部来添加令牌头部。

有时,颁发不带时间戳的令牌可能很有用(例如,测试、开发时间),在这种情况下,应将属性 noTimestamp 设置为 true(默认为 false)。这意味着令牌中没有 iat 字段。

令牌过期由属性 expiresInSeconds 控制,默认情况下没有过期。其他控制字段 audienceissuersubject 如果在配置中可用,则会被选中并添加到令牌元数据中。

最后,令牌以正确的格式签名和编码。