编辑此页面

构建、签名和加密 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,请将以下依赖项添加到您的项目中

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt-build</artifactId>
</dependency>
build.gradle
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 时会执行两次加密操作

  1. 生成的內容加密密钥使用提供的密钥和密钥加密算法 (如 RSA-OAEP) 进行加密。

  2. 声明使用内容加密密钥和内容加密算法 (如 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.locationsmallrye.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.locationsmallrye.jwt.encrypt.key.location 属性来指定签名和加密密钥的位置。这些密钥可以位于本地文件系统、类路径上,或从远程端点获取。密钥可以是 PEMJSON Web Key (JWK) 格式。例如

smallrye.jwt.sign.key.location=privateKey.pem
smallrye.jwt.encrypt.key.location=publicKey.pem

或者,您可以使用 MicroProfile ConfigSourcesmallrye.jwt.sign.keysmallrye.jwt.encrypt.key 属性从外部服务 (如 HashiCorp Vault 或其他密钥管理器) 获取密钥。

smallrye.jwt.sign.key=${private.key.from.vault}
smallrye.jwt.encrypt.key=${public.key.from.vault}

在此示例中,private.key.from.vaultpublic.key.from.vault 是自定义 ConfigSource 提供的 PEMJWK 格式的密钥值。

smallrye.jwt.sign.keysmallrye.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.locationsmallrye.jwt.encrypt.key.location 属性来引用密钥。

JWK 示例
{
 "kty":"oct",
 "kid":"secretKey",
 "k":"Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I"
}
JWK Set 示例
{
 "keys": [
   {
     "kty":"oct",
     "kid":"secretKey1",
     "k":"Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I"
   },
   {
     "kty":"oct",
     "kid":"secretKey2",
     "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
   }
 ]
}

您还可以使用 io.smallrye.jwt.util.KeyUtils 生成一对非对称 RSA 或 EC 密钥。这些密钥可以存储在 JWKJWK SetPEM 格式中。

SmallRye JWT Builder 配置

SmallRye JWT 支持以下属性,可用于自定义声明的签名或加密方式。

属性名称 默认 描述

smallrye.jwt.sign.key.location

当调用无参数的 sign()innerSign() 方法时,用于签名声明的私钥的位置。

smallrye.jwt.sign.key

当调用无参数的 sign()innerSign() 方法时,用于签名声明的密钥值。

smallrye.jwt.sign.key.id

签名密钥标识符,仅在使用 JWK 密钥时检查。

smallrye.jwt.encrypt.key.location

当调用无参数的 encrypt() 方法时,用于加密声明或内部 JWT 的公钥的位置。

smallrye.jwt.sign.relax-key-validation

false

放宽签名密钥的验证。

smallrye.jwt.encrypt.key

当调用无参数的 encrypt() 方法时,用于加密声明或内部 JWT 的密钥值。

smallrye.jwt.encrypt.key.id

加密密钥标识符,仅在使用 JWK 密钥时检查。

smallrye.jwt.encrypt.relax-key-validation

false

放宽加密密钥的验证。

smallrye.jwt.new-token.signature-algorithm

RS256

签名算法。当 JWT 签名构建器未设置签名算法时检查。

smallrye.jwt.new-token.key-encryption-algorithm

RSA-OAEP

密钥加密算法。当 JWT 加密构建器未设置密钥加密算法时检查。

smallrye.jwt.new-token.content-encryption-algorithm

A256GCM

内容加密算法。当 JWT 加密构建器未设置内容加密算法时检查。

smallrye.jwt.new-token.lifespan

300

token 有效期(秒),用于计算 exp (过期时间) 声明值,如果该声明未设置。

smallrye.jwt.new-token.issuer

token 签发者,用于设置 iss (签发者) 声明值,如果该声明未设置。

smallrye.jwt.new-token.audience

token 受众,用于设置 aud (受众) 声明值,如果该声明未设置。

smallrye.jwt.new-token.override-matching-claims

false

将此属性设置为 true,以使 smallrye.jwt.new-token.issuersmallrye.jwt.new-token.audience 的值覆盖已初始化的 iss (签发者) 和 aud (受众) 声明。

smallrye.jwt.new-token.add-default-claims

true

将此属性设置为 false,以禁用在未设置 iat (签发时间)、exp (过期时间) 和 jti (token 标识符) 声明时自动添加这些声明。

smallrye.jwt.keystore.type

JKS

smallrye.jwt.sign.key.locationsmallrye.jwt.encrypt.key.location 或两者都指向 KeyStore 文件时,此属性可用于自定义密钥库类型。如果未设置,则会检查文件名以确定密钥库类型,然后默认为 JKS

smallrye.jwt.keystore.provider

smallrye.jwt.sign.key.locationsmallrye.jwt.encrypt.key.location 指向 KeyStore 文件时,此属性可用于自定义 KeyStore 提供程序。

smallrye.jwt.keystore.password

密钥库密码。如果 smallrye.jwt.sign.key.locationsmallrye.jwt.encrypt.key.location 指向 KeyStore 文件,则必须设置此属性。

smallrye.jwt.keystore.encrypt.key.alias

smallrye.jwt.encrypt.key.location 指向 KeyStore 文件时,必须设置此属性以识别从匹配证书的 KeyStore 中提取的公钥。

smallrye.jwt.keystore.sign.key.alias

smallrye.jwt.sign.key.location 指向 KeyStore 文件时,必须设置此属性以识别私有签名密钥。

smallrye.jwt.keystore.sign.key.password

smallrye.jwt.sign.key.location 指向 KeyStore 文件,并且私有签名密钥的密码与 smallrye.jwt.keystore.password 不同时,可以设置此属性。

相关内容