构建、签名和加密 JSON Web Tokens
JSON Web Token (JWT) 由 RFC 7519 规范定义,作为一种紧凑、URL 安全的声明表示方式。这些声明被编码为 JSON 对象,并可用作 JSON Web Signature (JWS) 结构的载荷或 JSON Web Encryption (JWE) 结构的明文。此机制允许声明被数字签名或使用消息认证码 (MAC) 进行完整性保护,并进行加密。
签名声明是保护声明的最常见方法。通常,JWT token 是通过签名格式化为 JSON 的声明生成的,遵循 JSON Web Signature (JWS) 规范中概述的步骤。
当声明包含敏感信息时,可以通过使用 JSON Web Encryption (JWE) 规范来确保其机密性。这种方法会生成一个带有加密声明的 JWT。
为了增强安全性,您可以结合使用这两种方法:先签名声明,然后加密生成的嵌套 JWT。此过程可确保声明的机密性和完整性。
SmallRye JWT Build API 通过支持所有这些选项,简化了 JWT 声明的保护。它在内部使用 Jose4J 库来提供此功能。
依赖
要使用 SmallRye JWT Build API,请将以下依赖项添加到您的项目中
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt-build</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-jwt-build")
您可以独立使用 SmallRye JWT Build API,而无需创建由 quarkus-smallrye-jwt
扩展支持的 MicroProfile JWT 端点。
创建 JwtClaimsBuilder 并设置声明
第一步是初始化一个 JwtClaimsBuilder
,可以使用以下选项之一,并向其中添加一些声明
import java.util.Collections;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import io.smallrye.jwt.build.Jwt;
import io.smallrye.jwt.build.JwtClaimsBuilder;
import org.eclipse.microprofile.jwt.JsonWebToken;
...
// Create an empty builder and add some claims
JwtClaimsBuilder builder1 = Jwt.claims();
builder1.claim("customClaim", "custom-value").issuer("https://issuer.org");
// Alternatively, start with claims directly:
// JwtClaimsBuilder builder1 = Jwt.upn("Alice");
// Create a builder from an existing claims file
JwtClaimsBuilder builder2 = Jwt.claims("/tokenClaims.json");
// Create a builder from a map of claims
JwtClaimsBuilder builder3 = Jwt.claims(Collections.singletonMap("customClaim", "custom-value"));
// Create a builder from a JsonObject
JsonObject userName = Json.createObjectBuilder().add("username", "Alice").build();
JsonObject userAddress = Json.createObjectBuilder().add("city", "someCity").add("street", "someStreet").build();
JsonObject json = Json.createObjectBuilder(userName).add("address", userAddress).build();
JwtClaimsBuilder builder4 = Jwt.claims(json);
// Create a builder from a JsonWebToken
@Inject JsonWebToken token;
JwtClaimsBuilder builder5 = Jwt.claims(token);
该 API 是流式的,因此您可以将构建器的初始化作为流式序列的一部分。
如果未明确配置,构建器会自动设置以下声明
-
iat
(签发时间):当前时间 -
exp
(过期时间):从当前时间起五分钟后 (可通过smallrye.jwt.new-token.lifespan
属性自定义) -
jti
(唯一 token 标识符)
您可以全局配置以下属性,以避免直接在构建器中设置它们
-
smallrye.jwt.new-token.issuer
:指定默认的签发者。 -
smallrye.jwt.new-token.audience
:指定默认的受众。
初始化和设置声明后,下一步是决定如何保护声明。
签名声明
您可以立即签名声明,也可以在配置 JSON Web Signature (JWS)
标头后签名。
import io.smallrye.jwt.build.Jwt;
...
// Sign the claims using an RSA private key loaded from the location specified by the 'smallrye.jwt.sign.key.location' property.
// No 'jws()' transition is required. The default algorithm is RS256.
String jwt1 = Jwt.claims("/tokenClaims.json").sign();
// Set the headers and sign the claims by using an RSA private key loaded in the code (the implementation of this method is omitted).
// Includes a 'jws()' transition to a 'JwtSignatureBuilder'. The default algorithm is RS256.
String jwt2 = Jwt.claims("/tokenClaims.json")
.jws()
.keyId("kid1")
.header("custom-header", "custom-value")
.sign(getPrivateKey());
默认行为
-
alg
(算法) 标头默认设置为RS256
。 -
如果使用了包含
kid
属性的单个 JSON Web Key (JWK),则不必设置签名密钥标识符 (kid
标头)。
支持的密钥和算法
-
要签名声明,您可以使用 RSA 私钥、椭圆曲线 (EC) 私钥和对称密钥。
-
RS256
是默认的 RSA 私钥签名算法。 -
ES256
是默认的 EC 私钥签名算法。 -
HS256
是默认的对称密钥签名算法。
要自定义签名算法,请使用 JwtSignatureBuilder
API。例如
import io.smallrye.jwt.SignatureAlgorithm;
import io.smallrye.jwt.build.Jwt;
// Sign the claims using an RSA private key loaded from the location set with a 'smallrye.jwt.sign.key.location' property. The algorithm is PS256.
String jwt = Jwt.upn("Alice").jws().algorithm(SignatureAlgorithm.PS256).sign();
或者,您也可以使用以下属性全局配置签名算法
smallrye.jwt.new-token.signature-algorithm=PS256
这种方法提供了更简单的 API 序列
import io.smallrye.jwt.build.Jwt;
// Sign the claims using an RSA private key loaded from the location set with a 'smallrye.jwt.sign.key.location' property. The algorithm is PS256.
String jwt = Jwt.upn("Alice").sign();
您可以将 sign
步骤与 encrypt 步骤结合起来,创建 内嵌签名并加密
的 token。有关更多信息,请参阅 签名声明并加密嵌套的 JWT token 部分。
加密声明
您可以立即加密声明,也可以在设置 JSON Web Encryption (JWE)
标头后加密,这与签名声明类似。但是,加密声明始终需要一个 jwe()
过渡到 JwtEncryptionBuilder
,因为 API 经过优化,支持签名和内嵌签名操作。
import io.smallrye.jwt.build.Jwt;
...
// Encrypt the claims using an RSA public key loaded from the location specified by the 'smallrye.jwt.encrypt.key.location' property.
// The default key encryption algorithm is RSA-OAEP.
String jwt1 = Jwt.claims("/tokenClaims.json").jwe().encrypt();
// Set the headers and encrypt the claims by using an RSA public key loaded in the code (the implementation of this method is omitted).
// The default key encryption algorithm is A256KW.
String jwt2 = Jwt.claims("/tokenClaims.json").jwe().header("custom-header", "custom-value").encrypt(getSecretKey());
默认行为
-
alg
(密钥管理算法) 标头默认为RSA-OAEP
。 -
enc
(内容加密) 标头默认为A256GCM
。
支持的密钥和算法
-
您可以使用 RSA 公钥、椭圆曲线 (EC) 公钥和对称密钥来加密声明。
-
RSA-OAEP
是默认的 RSA 公钥加密算法。 -
ECDH-ES
是默认的 EC 公钥加密算法。 -
A256KW
是默认的对称密钥加密算法。
请注意,在创建加密 token 时会执行两次加密操作
-
生成的內容加密密钥使用提供的密钥和密钥加密算法 (如
RSA-OAEP
) 进行加密。 -
声明使用内容加密密钥和内容加密算法 (如
A256GCM
) 进行加密。
您可以使用 JwtEncryptionBuilder
API 来自定义密钥和内容加密算法。例如
import io.smallrye.jwt.KeyEncryptionAlgorithm;
import io.smallrye.jwt.ContentEncryptionAlgorithm;
import io.smallrye.jwt.build.Jwt;
// Encrypt the claims using an RSA public key loaded from the location set with a 'smallrye.jwt.encrypt.key.location' property.
// Key encryption algorithm is RSA-OAEP-256. The content encryption algorithm is A256CBC-HS512.
String jwt = Jwt.subject("Bob").jwe()
.keyAlgorithm(KeyEncryptionAlgorithm.RSA_OAEP_256)
.contentAlgorithm(ContentEncryptionAlgorithm.A256CBC_HS512)
.encrypt();
或者,您也可以使用以下属性全局配置算法
smallrye.jwt.new-token.key-encryption-algorithm=RSA-OAEP-256
smallrye.jwt.new-token.content-encryption-algorithm=A256CBC-HS512
此配置允许更简单的 API 序列
import io.smallrye.jwt.build.Jwt;
// Encrypt the claims by using an RSA public key loaded from the location set with a 'smallrye.jwt.encrypt.key.location' property.
// Key encryption algorithm is RSA-OAEP-256. The content encryption algorithm is A256CBC-HS512.
String jwt = Jwt.subject("Bob").encrypt();
安全 token 加密的建议
-
当 token 直接使用 RSA 或 EC 公钥加密时,无法验证哪个方发送了 token。为了解决这个问题,建议使用对称密钥进行直接加密,尤其是在 JWT 用作由 Quarkus 端点单独管理的 cookie 时。
-
要使用 RSA 或 EC 公钥加密 token,如果存在签名密钥,建议先对 token 进行签名。有关更多信息,请参阅 签名声明并加密嵌套的 JWT token 部分。
签名声明并加密嵌套的 JWT token
您可以通过结合签名和加密步骤来签名声明,然后加密嵌套的 JWT token。
import io.smallrye.jwt.build.Jwt;
...
// Sign the claims and encrypt the nested token using the private and public keys loaded from the locations
// specified by the 'smallrye.jwt.sign.key.location' and 'smallrye.jwt.encrypt.key.location' properties, respectively.
// The signature algorithm is RS256, and the key encryption algorithm is RSA-OAEP-256.
String jwt = Jwt.claims("/tokenClaims.json").innerSign().encrypt();
快速 JWT 生成
如果设置了 smallrye.jwt.sign.key.location
或 smallrye.jwt.encrypt.key.location
属性,您可以一键保护现有的声明,例如资源、映射、JsonObject。
// More compact than Jwt.claims("/claims.json").sign();
Jwt.sign("/claims.json");
// More compact than Jwt.claims("/claims.json").jwe().encrypt();
Jwt.encrypt("/claims.json");
// More compact than Jwt.claims("/claims.json").innerSign().encrypt();
Jwt.signAndEncrypt("/claims.json");
如前所述,以下声明将在未设置时自动添加:iat
(签发时间)、exp
(过期时间)、jti
(token 标识符)、iss
(签发者) 和 aud
(受众)。
处理密钥
您可以使用 smallrye.jwt.sign.key.location
和 smallrye.jwt.encrypt.key.location
属性来指定签名和加密密钥的位置。这些密钥可以位于本地文件系统、类路径上,或从远程端点获取。密钥可以是 PEM
或 JSON Web Key (JWK)
格式。例如
smallrye.jwt.sign.key.location=privateKey.pem
smallrye.jwt.encrypt.key.location=publicKey.pem
或者,您可以使用 MicroProfile ConfigSource
和 smallrye.jwt.sign.key
及 smallrye.jwt.encrypt.key
属性从外部服务 (如 HashiCorp Vault 或其他密钥管理器) 获取密钥。
smallrye.jwt.sign.key=${private.key.from.vault}
smallrye.jwt.encrypt.key=${public.key.from.vault}
在此示例中,private.key.from.vault
和 public.key.from.vault
是自定义 ConfigSource
提供的 PEM
或 JWK
格式的密钥值。
smallrye.jwt.sign.key
和 smallrye.jwt.encrypt.key
属性也可以直接包含 Base64 编码的私钥或公钥值。
但是,请注意,不建议直接在配置中内联私钥。仅在需要从远程密钥管理器获取签名密钥值时使用 smallrye.jwt.sign.key
属性。
密钥也可以由构建 token 的代码加载,然后提供给 JWT Build API 进行 token 创建。
如果您需要使用对称密钥签名或加密 token,请考虑使用 io.smallrye.jwt.util.KeyUtils
为所需长度生成 SecretKey
。
例如,使用 HS512
算法 (512/8
) 签名 token 需要一个 64 字节的密钥,使用 A256KW
算法 (256/8
) 加密内容加密密钥需要一个 32 字节的密钥。
import javax.crypto.SecretKey;
import io.smallrye.jwt.KeyEncryptionAlgorithm;
import io.smallrye.jwt.SignatureAlgorithm;
import io.smallrye.jwt.build.Jwt;
import io.smallrye.jwt.util.KeyUtils;
SecretKey signingKey = KeyUtils.generateSecretKey(SignatureAlgorithm.HS512);
SecretKey encryptionKey = KeyUtils.generateSecretKey(KeyEncryptionAlgorithm.A256KW);
String jwt = Jwt.claim("sensitiveClaim", getSensitiveClaim()).innerSign(signingKey).encrypt(encryptionKey);
您还可以考虑使用 JSON Web Key (JWK) 或 JSON Web Key Set (JWK Set) 格式将密钥存储在安全的文件系统中。您可以使用 smallrye.jwt.sign.key.location
或 smallrye.jwt.encrypt.key.location
属性来引用密钥。
{
"kty":"oct",
"kid":"secretKey",
"k":"Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I"
}
{
"keys": [
{
"kty":"oct",
"kid":"secretKey1",
"k":"Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I"
},
{
"kty":"oct",
"kid":"secretKey2",
"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
}
]
}
您还可以使用 io.smallrye.jwt.util.KeyUtils
生成一对非对称 RSA 或 EC 密钥。这些密钥可以存储在 JWK
、JWK Set
或 PEM
格式中。
SmallRye JWT Builder 配置
SmallRye JWT 支持以下属性,可用于自定义声明的签名或加密方式。
属性名称 | 默认 | 描述 |
---|---|---|
|
|
当调用无参数的 |
|
|
当调用无参数的 |
|
|
签名密钥标识符,仅在使用 JWK 密钥时检查。 |
|
|
当调用无参数的 |
|
|
放宽签名密钥的验证。 |
|
|
当调用无参数的 |
|
|
加密密钥标识符,仅在使用 JWK 密钥时检查。 |
|
|
放宽加密密钥的验证。 |
|
|
签名算法。当 JWT 签名构建器未设置签名算法时检查。 |
|
|
密钥加密算法。当 JWT 加密构建器未设置密钥加密算法时检查。 |
|
|
内容加密算法。当 JWT 加密构建器未设置内容加密算法时检查。 |
|
|
token 有效期(秒),用于计算 |
|
|
token 签发者,用于设置 |
|
|
token 受众,用于设置 |
|
|
将此属性设置为 |
|
|
将此属性设置为 |
|
|
当 |
|
当 |
|
|
密钥库密码。如果 |
|
|
当 |
|
|
当 |
|
|
当 |