<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId>
<version>5.2.1</version>
</dependency>
JWT 身份认证与授权提供者 (vertx-auth-jwt)
该组件提供了开箱即用的 JSON Web Token(JWT)反应式安全实现。 要在项目中使用该组件,请在构建描述文件的 dependencies 区域添加以下依赖:
-
Maven(在
pom.xml中):
-
Gradle(在
build.gradle文件中):
compile 'io.vertx:vertx-auth-jwt:5.2.1'
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"));
通过 JSON 配置密钥
上述所有配置项均支持从 JSON 构建,例如直接从配置文件中读取。
请特别注意:在 JSON 中,PubSecKeyOptions 的 buffer 对应 Buffer 类型,在 JSON 序列化中表示为其字节数组的 Base64 编码,而 不是 PEM 原始文本内容。若直接将原始 PEM 字符串作为 buffer 传入,将抛出 IllegalArgumentException: Illegal base64 character 异常。
要获取密钥的规范 JSON 形式,请对 PEM(或 Secret)进行 Base64 编码,或者让配置选项类自动完成转换:
String publicKeyPem =
"-----BEGIN PUBLIC KEY-----\n" +
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEraVJ8CpkrwTPRCPluUDdwC6b8+m4\n" +
"dEjwl8s+Sn0GULko+H95fsTREQ1A2soCFHS4wV3/23Nebq9omY3KuK9DKw==\n" +
"-----END PUBLIC KEY-----";
// 密钥的 JSON 表示形式,其中 "buffer" 经由 Base64 编码:
// {"algorithm":"ES256","buffer":"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0K..."}
JsonObject key = new PubSecKeyOptions()
.setAlgorithm("ES256")
.setBuffer(publicKeyPem)
.toJson();
// 随后可存储至配置文件并在应用启动时加载:
JsonObject config = new JsonObject()
.put("pubSecKeys", new JsonArray().add(key));
JWTAuth provider = JWTAuth.create(vertx, new JWTAuthOptions(config));
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 进行校验。
验证过程执行以下步骤:
-
如果
ignoreExpiration(默认 false)为 false,则检查令牌是否过期。该过程会依次检验exp、iat与nbf字段。 考虑到服务器之间可能存在时钟漂移,可以配置一定的leeway(时间容差),在时间略微超出限制时提供宽限期。 -
如果配置了
audience,则核对令牌中的aud是否与之一致,且所有预设的 audience 必须均存在于令牌中。 -
如果配置了
issuer,则核对令牌中的iss是否与预设的签发者一致。
上述所有验证均通过后,将返回一个 JWTUser 对象。该对象内部引用了配置中指定的权限声明键,在后续进行授权操作时使用。该值指向应检查权限凭证的 JSON 路径。
自定义令牌生成
与令牌验证类似,令牌生成行为在初始化配置中预设。
在生成令牌时,可以传入一个可选的 JWTOptions 参数来精确控制生成细节:
可以使用属性 algorithm 配置令牌签名算法(默认为 HS256),此时系统会查找与该算法对应的密钥进行签名。
若需要向默认标头合并附加信息,可以通过选项中的 headers 属性传入自定义 Header。
有时在测试或本地开发阶段,签发不带时间戳的令牌非常方便;此时可以将属性 noTimestamp 设置为 true(默认为 false),此时生成的令牌中将不包含 iat 字段。
令牌的有效期通过 expiresInSeconds 属性控制,默认不设置过期时间。
配置中的其他控制字段如 audience、issuer 与 subject 只要存在,均会被自动提取并写入令牌元数据中。
最后,令牌被计算签名并编码为规范的标准字符串格式。