JSON Web トークン (JWT) のビルド、署名、暗号化
JSON Web Token (JWT) は、RFC 7519 仕様で定義されており、コンパクトで URL セーフな形式でクレームを表現する手段です。 クレームは JSON オブジェクトとしてエンコードされ、JSON Web Signature (JWS) 構造のペイロードとして使用することも、JSON Web Encryption (JWE) 構造の平文として使用することもできます。 この仕組みにより、クレームはデジタル署名されるか、メッセージ認証コード (MAC) によって整合性が保護され、さらに暗号化することが可能になります。
クレームの署名は、クレームを保護する最も一般的な方法です。 通常、JWT トークンは JSON 形式のクレームに署名することで生成されますが、これは JSON Web Signature (JWS) 仕様に基づいた手順で行われます。
クレームに機密情報が含まれる場合、JSON Web Encryption (JWE) 仕様を使用することで、その機密性を確保できます。 この方法では、クレームが暗号化された JWT が生成されます。
セキュリティーを強化するために、両方の方法を組み合わせることができます。 まずクレームに署名し、その後、作成された JWT をネスト化し、暗号化します。 このプロセスにより、クレームの機密性と整合性の両方が確保されます。
SmallRye JWT Build API は、これらすべてのオプションをサポートすることで、JWT クレームの保護を簡素化します。 この機能を提供するために、内部で Jose4J ライブラリーを使用しています。
依存関係
SmallRye JWT ビルド 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 の作成とクレームの設定
最初のステップとして、以下のオプションの 1 つを使用して 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 は Fluent であるため、Fluent Sequence の一部としてビルダーを初期化できます。
明示的に設定されていない場合、ビルダーは次のクレームを自動的に設定します。
-
iat
(発行時刻): 現在の時刻 -
exp
(有効期限): 現在の時刻から 5 分後 (smallrye.jwt.new-token.lifespan
プロパティーでカスタマイズ可能) -
jti
(一意のトークン識別子)
ビルダーで直接設定しなくても済むように、次のプロパティーをグローバルに設定できます。
-
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
(algorithm) ヘッダーはデフォルトで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 のステップを組み合わせることで、 内側が署名され、暗号化された
トークンを作成できます。
詳細は、Sign the claims and encrypt the nested JWT token セクションを参照してください。
クレームの暗号化
クレームは、署名方法と同様に、すぐに暗号化することも、 JSON Web Encryption (JWE)
ヘッダーを設定した後に暗号化することもできます。
ただし、API は署名および内部署名操作をサポートするように最適化されているため、クレームを暗号化するには、常に jwe()
から JwtEncryptionBuilder
への遷移が必要です。
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
はデフォルトの対称鍵暗号化アルゴリズムです。
暗号化されたトークンを作成する際には、2つの暗号化処理が行われることに注意してください。
-
生成されたコンテンツ暗号鍵は、提供されたキーと 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();
安全なトークン暗号化に関する推奨事項:
-
トークンが公開 RSA または EC キーで直接暗号化されると、どの当事者がトークンを送信したかを確認できません。 これに対処するには、特に Quarkus エンドポイントによってのみ管理される Cookie として JWT を使用する場合、直接暗号化には対称秘密鍵が推奨されます。
-
RSA または EC 公開鍵でトークンを暗号化する場合、署名鍵が利用可能な場合は、まずトークンに署名することを推奨します。詳細は、Sign the claims and encrypt the nested JWT token セクションを参照してください。
クレームへの署名とネストされた JWT トークンの暗号化
クレームに署名し、署名と暗号化のステップを組み合わせてネストされた JWT トークンを暗号化できます。
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 などの既存のクレームを 1 回の呼び出しで保護できます。
// 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
(トークン識別子)、 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
または、リンクなどの外部サービスからキーを取得することもできます。https://docs.quarkiverse.io/quarkus-vault/dev/index.html [HashiCorp Vault] またはその他のシークレットマネージャーでは、MicroProfile の ConfigSource
と smallrye.jwt.sign.key
および smallrye.jwt.encrypt.key
プロパティーを使用します。
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
プロパティーを使用してください。
キーは、トークンを構築するコードによって読み込まれ、トークン作成のために JWT ビルド API に渡すこともできます。
対称秘密鍵を使用してトークンに署名または暗号化する必要がある場合は、 io.smallrye.jwt.util.KeyUtils
を使用して必要な長さの SecretKey
を生成することを検討してください。
たとえば、 HS512
アルゴリズム (512/8
) を使用してトークンに署名するには 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 暗号化ビルダーがコンテンツ暗号化アルゴリズムをまだ設定していないかどうかを確認します。 |
|
|
このクレームがまだ設定されていない場合に、 |
|
|
このクレームがまだ設定されていない場合に、 |
|
|
このクレームがまだ設定されていない場合に、 |
|
|
既に初期化されている |
|
|
このプロパティは、 |
|
このプロパティは、 |
|
|
キーストアのパスワード。 |
|
|
|
|
|
このプロパティは、 |
|
|
このプロパティーは、 |