MicroProfile JSON Web Token (JWT) 認証
概要
Red Hat build of Quarkus ドキュメントへのフィードバックの提供
エラーを報告したり、ドキュメントを改善したりするには、Red Hat Jira アカウントにログインし、課題を送信してください。Red Hat Jira アカウントをお持ちでない場合は、アカウントを作成するように求められます。
手順
- 次のリンクをクリックして チケットを作成します。
- Summary に課題の簡単な説明を入力します。
- Description に課題や機能拡張の詳細な説明を入力します。問題があるドキュメントのセクションへの URL も記載してください。
- Submit をクリックすると、課題が作成され、適切なドキュメントチームに転送されます。
第1章 JWT RBAC の使用
このガイドでは 、SmallRye JWT を Quarkus アプリケーションに統合して、MicroProfile JWT 仕様に準拠した JSON Web Token (JWT) セキュリティーを実装する方法について説明します。JWT を検証し、MicroProfile JWT org.eclipse.microprofile.jwt.JsonWebToken
として表現し、ベアラートークン認可と ロールベースアクセス制御 を使用して Quarkus HTTP エンドポイントを保護する方法を学習します。
Quarkus OpenID Connect quarkus-oidc
エクステンションは Bearer Token Authorization もサポートし、smallrye-jwt
を使用してベアラートークンを JsonWebToken
として表します。詳細は、OIDC ベアラートークン認証 ガイドを参照してください。
Quarkus アプリケーションで OIDC 認可コードフローを使用してユーザーを認証する必要がある場合は、OpenID Connect 拡張機能を使用する必要があります。詳細は、Web アプリケーションを保護するための OIDC コードフローメカニズム を参照してください。
1.1. 前提条件
このガイドを完了するには、以下が必要です。
- 約 15 分
- IDE
-
JAV_HOME
が適切に設定された状態でインストールされた JDK 17 以降 - Apache Maven 3.8.6 以降
- オプションで使用する場合は Quarkus CLI
- ネイティブ実行可能ファイル (ネイティブコンテナービルドを使用する場合は Docker) をビルドする必要がある場合は、オプションで Mandrel または GraalVM がインストールされ、適切に設定されている
1.2. Quickstart
1.2.1. ソリューション
アプリケーションを段階的に作成するには、次のセクションの指示に従うことを推奨します。必要に応じて、完成した例に進むこともできます。
例にアクセスするには、Git リポジトリーのクローンを作成するか、アーカイブをダウンロードします。
-
リポジトリーのクローンを作成します:
git clone https://github.com/quarkusio/quarkus-quickstarts.git -b 3.20
。 - アーカイブ をダウンロードしてください。
完成したソリューションは 、security-jwt-quickstart
ディレクトリー にあります。
1.2.2. Maven プロジェクトの作成
まず、以下のコマンドで新しいプロジェクトを作成します。
Quarkus CLI を使用:
quarkus create app org.acme:security-jwt-quickstart \ --extension='rest-jackson,smallrye-jwt,smallrye-jwt-build' \ --no-code cd security-jwt-quickstart
Gradle プロジェクトを作成するには、
--gradle
オプションまたは--gradle-kotlin-dsl
オプションを追加します。Quarkus CLI のインストール方法と使用方法の詳細は、Quarkus CLI ガイドを参照してください。
Maven を使用:
mvn com.redhat.quarkus.platform:quarkus-maven-plugin:3.20.1:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=security-jwt-quickstart \ -Dextensions='rest-jackson,smallrye-jwt,smallrye-jwt-build' \ -DnoCode cd security-jwt-quickstart
Gradle プロジェクトを作成するには、
-DbuildTool=gradle
または-DbuildTool=gradle-kotlin-dsl
オプションを追加します。
Windows ユーザーの場合:
-
cmd を使用する場合は、バックスラッシュ
\
を使用せず、すべてを同じ行に記述してください。 -
Powershell を使用する場合は、
-D
パラメーターを二重引用符で囲みます (例:"-DprojectArtifactId=security-jwt-quickstart"
)。
このコマンドは、Maven プロジェクトを生成し、MicroProfile JWT RBAC サポートを含む smallrye-jwt
エクステンションをインポートします。
Quarkus プロジェクトがすでに設定されている場合は、プロジェクトベースディレクトリーで次のコマンドを実行して、smallrye-jwt
エクステンションをプロジェクトに追加できます。
Quarkus CLI を使用:
quarkus extension add smallrye-jwt,smallrye-jwt-build
Maven を使用:
./mvnw quarkus:add-extension -Dextensions='smallrye-jwt,smallrye-jwt-build'
Gradle を使用:
./gradlew addExtension --extensions='smallrye-jwt,smallrye-jwt-build'
このコマンドは、ビルドファイルに次の依存関係を追加します。
Maven を使用:
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-jwt</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-jwt-build</artifactId> </dependency>
Gradle を使用:
implementation("io.quarkus:quarkus-smallrye-jwt") implementation("io.quarkus:quarkus-smallrye-jwt-build")
1.2.3. Jakarta REST リソースの検証
以下の内容を含む src/main/java/org/acme/security/jwt/TokenSecuredResource.java
に REST エンドポイントを作成します。
REST エンドポイント V1
package org.acme.security.jwt; import jakarta.annotation.security.PermitAll; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.SecurityContext; import org.eclipse.microprofile.jwt.JsonWebToken; @Path("/secured") public class TokenSecuredResource { @Inject JsonWebToken jwt; 1 @GET @Path("permit-all") @PermitAll 2 @Produces(MediaType.TEXT_PLAIN) public String hello(@Context SecurityContext ctx) { return getResponseString(ctx); 3 } private String getResponseString(SecurityContext ctx) { String name; if (ctx.getUserPrincipal() == null) { 4 name = "anonymous"; } else if (!ctx.getUserPrincipal().getName().equals(jwt.getName())) { 5 throw new InternalServerErrorException("Principal and JsonWebToken names do not match"); } else { name = ctx.getUserPrincipal().getName(); 6 } return String.format("hello %s," + " isHttps: %s," + " authScheme: %s," + " hasJWT: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJwt()); 7 } private boolean hasJwt() { return jwt.getClaimNames() != null; } }
- 1
JsonWebToken
インターフェイスが挿入され、現在の認証済みトークンに関連付けられたクレームへのアクセスが提供されます。このインターフェイスはjava.security.Principal
を拡張します。- 2
@PermitAll
は標準の Jakarta セキュリティーアノテーションです。これは、認証されているかどうかに関係なく、指定されたエンドポイントがすべての呼び出し元からアクセスできることを示します。- 3
- リクエストのセキュリティー状態を検査するために、Jakarta REST
SecurityContext が
挿入されます。getResponseString()
関数は応答を生成します。 - 4
- 要求ユーザー/呼び出し元の
プリンシパルが
null かどうかをチェックして、呼び出しが安全でないかどうかを確認します。 - 5
JsonWebToken は
現在のPrincipal
を表すため、Principal
とJsonWebToken
の名前が一致することを確認します。- 6
プリンシパル
の名前を取得します。- 7
- 呼び出し元の名前、リクエスト
SecurityContext
のisSecure()
およびgetAuthenticationScheme()
の状態、および null 以外のJsonWebToken
が挿入されたかどうかを含む応答を構築します。
1.2.4. 開発モードでのアプリケーションの実行
これで、次のいずれかのコマンドを使用して、アプリケーションを開発モードで実行する準備が整いました。
Quarkus CLI を使用:
quarkus dev
Maven を使用:
./mvnw quarkus:dev
Gradle を使用:
./gradlew --console=plain quarkusDev
次の例のような出力が表示されるはずです。
quarkus:dev の出力
[INFO] Scanning for projects... [INFO] [INFO] ----------------------< org.acme:security-jwt-quickstart >----------------------- [INFO] Building security-jwt-quickstart 1.0.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- ... Listening for transport dt_socket at address: 5005 2020-07-15 16:09:50,883 INFO [io.quarkus] (Quarkus Main Thread) security-jwt-quickstart 1.0.0-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 1.073s. Listening on: http://0.0.0.0:8080 2020-07-15 16:09:50,885 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 2020-07-15 16:09:50,885 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, mutiny, rest, rest-jackson, security, smallrye-context-propagation, smallrye-jwt, vertx, vertx-web]
REST エンドポイントが実行されているので、curl などのコマンドラインツールを使用してアクセスできるようになります。
/secured/permit-all の curl コマンド
$ curl http://127.0.0.1:8080/secured/permit-all; echo
このコマンドは次の応答を返します。
hello anonymous, isHttps: false, authScheme: null, hasJWT: false
リクエストで JWT を提供していないため、エンドポイントでセキュリティー状態が確認されることはなく、応答もそれと一致しています。
- ユーザー名は匿名です。
-
https
が使用されていないため、isHttps
はfalse
です。 - authScheme は null です。
- hasJWT は false です。
Ctrl-C を使用して Quarkus サーバーを停止します。
それでは、実際に何かを保護しましょう。以下の新しいエンドポイントメソッド helloRolesAllowed
を確認してください。
REST エンドポイント V2
package org.acme.security.jwt; import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.SecurityContext; import org.eclipse.microprofile.jwt.JsonWebToken; @Path("/secured") public class TokenSecuredResource { @Inject JsonWebToken jwt; 1 @GET @Path("permit-all") @PermitAll @Produces(MediaType.TEXT_PLAIN) public String hello(@Context SecurityContext ctx) { return getResponseString(ctx); } @GET @Path("roles-allowed") 2 @RolesAllowed({ "User", "Admin" }) 3 @Produces(MediaType.TEXT_PLAIN) public String helloRolesAllowed(@Context SecurityContext ctx) { return getResponseString(ctx) + ", birthdate: " + jwt.getClaim("birthdate").toString(); 4 } private String getResponseString(SecurityContext ctx) { String name; if (ctx.getUserPrincipal() == null) { name = "anonymous"; } else if (!ctx.getUserPrincipal().getName().equals(jwt.getName())) { throw new InternalServerErrorException("Principal and JsonWebToken names do not match"); } else { name = ctx.getUserPrincipal().getName(); } return String.format("hello %s," + " isHttps: %s," + " authScheme: %s," + " hasJWT: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJwt()); } private boolean hasJwt() { return jwt.getClaimNames() != null; } }
TokenSecuredResource
にこれを追加したら、./mvnw compile quarkus:dev
コマンドを再実行します。続いて、curl -v http://127.0.0.1:8080/secured/roles-allowed; echo
を試して、新しいエンドポイントへのアクセスを試行します。
出力は、以下のようになります。
/secured/roles-allowed の curl コマンド
$ curl -v http://127.0.0.1:8080/secured/roles-allowed; echo
このコマンドは次の応答を返します。
* Trying 127.0.0.1... * TCP_NODELAY set * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) > GET /secured/roles-allowed HTTP/1.1 > Host: 127.0.0.1:8080 > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 401 Unauthorized < Connection: keep-alive < Content-Type: text/html;charset=UTF-8 < Content-Length: 14 < Date: Sun, 03 Mar 2019 16:32:34 GMT < * Connection #0 to host 127.0.0.1 left intact
Excellentリクエストに JWT が指定されなかったため、エンドポイントへのアクセスが正しく拒否されました。代わりに、HTTP 401 不正なエラーが発生しました。
エンドポイントにアクセスするには、有効な JWT を取得してリクエストに含める必要があります。これには 2 つのステップが含まれます。
- JWT を検証するために必要な情報を使用して SmallRye JWT 拡張機能を設定します。
- 設定に一致する適切なクレームを含む JWT を生成します。
1.2.5. SmallRye JWT エクステンションのセキュリティー情報の設定
以下の内容で security-jwt-quickstart/src/main/resources/application.properties
を作成します。
TokenSecuredResource の application.properties
mp.jwt.verify.publickey.location=publicKey.pem 1 mp.jwt.verify.issuer=https://example.com/issuer 2 quarkus.native.resources.includes=publicKey.pem 3
- 1
- クラスパス上の公開鍵ファイル
publicKey.pem
の場所を指定します。このキーを追加するには 、公開鍵の追加を 参照してください。 - 2
- 予想される発行者を
https://example.com/issuer
として定義します。 - 3
publicKey.pem
ファイルがネイティブ実行可能ファイルのリソースとして含まれていることを確認します。
1.2.6. 公開鍵の追加
JWT 仕様 は、使用可能な JWT のさまざまなレベルを定義します。MicroProfile JWT RBAC 仕様では、RSA-256 署名アルゴリズムで署名された JWT が必要です。これには、RSA 公開鍵のペアが必要です。REST エンドポイントサーバー側では、リクエストとともに送信された JWT の検証に使用する RSA 公開鍵のロケーションを設定する必要があります。以前に設定された mp.jwt.verify.publickey.location=publicKey.pem
設定は、公開鍵が publicKey.pem
としてクラスパスで利用可能であることを想定しています。これを実行するには、以下の内容を security-jwt-quickstart/src/main/resources/publicKey.pem
ファイルにコピーします。
RSA 公開鍵の PEM コンテンツ
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9 AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x nQIDAQAB -----END PUBLIC KEY-----
1.2.7. JWT の生成
多くの場合、Keycloak などのアイデンティティーマネージャーから JWT を取得します。ただし、このクイックスタートでは、smallrye-jwt
によって提供される JWT 生成 API を使用して独自の JWT を生成します。詳細は、SmallRye JWT を使用した JWT トークンの生成 を参照してください。
以下のリストからコードを取得し、security-jwt-quickstart/src/test/java/org/acme/security/jwt/GenerateToken.java
に配置します。
GenerateToken メインドライバークラス
package org.acme.security.jwt; import java.util.Arrays; import java.util.HashSet; import org.eclipse.microprofile.jwt.Claims; import io.smallrye.jwt.build.Jwt; /** * A utility class to generate and print a JWT token string to stdout. */ public class GenerateToken { /** * Generates and prints a JWT token. */ public static void main(String[] args) { String token = Jwt.issuer("https://example.com/issuer") 1 .upn("jdoe@quarkus.io") 2 .groups(new HashSet<>(Arrays.asList("User", "Admin"))) 3 .claim(Claims.birthdate.name(), "2001-07-13") 4 .sign(); System.out.println(token); System.exit(0); } }
- 1
- JWT に
iss
(発行者) クレームを設定します。トークンが有効と見なされるためには、この値がサーバー側のmp.jwt.verify.issuer
設定と一致する必要があります。 - 2
- MicroProfile JWT RBAC 仕様では、コンテナーセキュリティー API で
プリンシパル
を識別するための推奨クレームとして定義されているupn
(ユーザープリンシパル名) クレームを指定します。 - 3
- JWT ベアラーに割り当てられたグループメンバーシップと最上位レベルのロールを提供する
グループ
クレームを定義します。 - 4
生年月日の
クレームを追加します。これは機密情報とみなされる可能性があるため、SmallRye JWT を使用して JWT トークンを生成する の説明に従ってクレームを暗号化することを検討してください。
このコードが機能するには、TokenSecuredResource
アプリケーションにある公開鍵に対応する RSA 秘密鍵の内容が必要であることに注意してください。以下の PEM コンテンツを取得し、security-jwt-quickstart/src/test/resources/privateKey.pem
に配置します。
RSA 秘密鍵の PEM コンテンツ
-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6 oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv 6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6 96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3 zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS 34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG 5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2 tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09 nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8 f3cg+fr8aou7pr9SHhJlZCU= -----END PRIVATE KEY-----
後で、smallrye.jwt.sign.key.location
プロパティーを設定して、秘密署名鍵の場所を指定します。
また、OpenSSL コマンドラインツールを使用して、公開鍵と秘密鍵のペアを生成することもできます。
キーを生成するための openssl
コマンド
openssl genrsa -out rsaPrivateKey.pem 2048 openssl rsa -pubout -in rsaPrivateKey.pem -out publicKey.pem
秘密鍵を生成し、安全なキーの保管と転送に一般的に使用される PKCS#8 形式に変換するには、追加の手順が必要です。
変換を実行するための openssl
コマンド
openssl pkcs8 -topk8 -nocrypt -inform pem -in rsaPrivateKey.pem -outform pem -out privateKey.pem
このクイックスタートで使用されているキーペアの代わりに、生成されたキーペアを使用できます。
TokenSecuredResource
エンドポイントの JSON Web Token (JWT) を生成する前に、アプリケーションが実行されていること を確認してください。
次に、次のコマンドを使用して JWT を生成します。
JWT 生成の出力例
$ mvn exec:java -Dexec.mainClass=org.acme.security.jwt.GenerateToken -Dexec.classpathScope=test -Dsmallrye.jwt.sign.key.location=privateKey.pem
JWT 文字列は、.
文字で区切られた 3 つの部分で設定される Base64 URL エンコードされた文字列です。
- 署名アルゴリズムなど、トークンに関するメタデータが含まれるヘッダー。
- ペイロードはクレームとも呼ばれ、トークンのクレームまたはデータが含まれます。
- トークンの整合性を検証する署名。
1.2.8. /secured/roles-allowed へのセキュアなアクセス。
次に、これを使用して、/secured/roles-allowed エンドポイントに対してセキュアなリクエストを行います。Quarkus サーバーが引き続き dev モードで実行されていることを確認してから以下のコマンドを実行し、前のステップで生成された JWT のバージョンを使用するようにしてください。
JWT を使用した /secured/roles-allowed の curl コマンド
$ curl -H "Authorization: Bearer eyJraWQ..." http://127.0.0.1:8080/secured/roles-allowed; echo
生成されたトークンを HTTP 認可ベアラースキームの値として必ず使用してください。
このコマンドは次の応答を返します。
hello jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13
Success! これで次のようになります。
-
匿名でない発信者名:
jdoe@quarkus.io
-
認証方式:
ベアラー
- null 以外の JsonWebToken
-
生年月日
請求額
1.2.9. JsonWebToken およびクレームインジェクションの使用
これで、セキュリティー保護された REST エンドポイントにアクセスするための JWT を生成できるようになったので、JsonWebToken
インターフェイスと JWT クレームを使用してさらに何ができるかを確認しましょう。org.eclipse.microprofile.jwt.JsonWebToken
インターフェイスは java.security.Principal
インターフェイスを拡張し、以前に使用した jakarta.ws.rs.core.SecurityContext#getUserPrincipal()
呼び出しによって返されるオブジェクトタイプです。これは、CDI を使用せずとも、REST コンテナー SecurityContext
にアクセスできるコードは、SecurityContext#getUserPrincipal()
をキャストすることで、呼び出し元の JsonWebToken
インターフェイスを取得できることを意味します。
JsonWebToken
インターフェイスは、基礎となる JWT のクレームにアクセスするためのメソッドを定義します。MicroProfile JWT RBAC 仕様で必要な一般的なクレームと、JWT 内に存在する可能性のある任意のクレームのアクセサーを提供します。
すべての JWT クレームも注入できます。TokenSecuredResource
を、(JsonWebToken
から取得するのではなく) 注入した birthdate
クレームを使用する別のエンドポイント /secured/roles-allowed-admin で拡張します。
package org.acme.security.jwt; import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.SecurityContext; import org.eclipse.microprofile.jwt.Claim; import org.eclipse.microprofile.jwt.Claims; import org.eclipse.microprofile.jwt.JsonWebToken; @Path("/secured") @RequestScoped 1 public class TokenSecuredResource { @Inject JsonWebToken jwt; 2 @Inject @Claim(standard = Claims.birthdate) String birthdate; 3 @GET @Path("permit-all") @PermitAll @Produces(MediaType.TEXT_PLAIN) public String hello(@Context SecurityContext ctx) { return getResponseString(ctx); } @GET @Path("roles-allowed") @RolesAllowed({ "User", "Admin" }) @Produces(MediaType.TEXT_PLAIN) public String helloRolesAllowed(@Context SecurityContext ctx) { return getResponseString(ctx) + ", birthdate: " + jwt.getClaim("birthdate").toString(); } @GET @Path("roles-allowed-admin") @RolesAllowed("Admin") @Produces(MediaType.TEXT_PLAIN) public String helloRolesAllowedAdmin(@Context SecurityContext ctx) { return getResponseString(ctx) + ", birthdate: " + birthdate; 4 } private String getResponseString(SecurityContext ctx) { String name; if (ctx.getUserPrincipal() == null) { name = "anonymous"; } else if (!ctx.getUserPrincipal().getName().equals(jwt.getName())) { throw new InternalServerErrorException("Principal and JsonWebToken names do not match"); } else { name = ctx.getUserPrincipal().getName(); } return String.format("hello %s," + " isHttps: %s," + " authScheme: %s," + " hasJWT: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJwt()); } private boolean hasJwt() { return jwt.getClaimNames() != null; } }
トークンを再度生成し、実行します。
$ curl -H "Authorization: Bearer eyJraWQ..." http://127.0.0.1:8080/secured/roles-allowed-admin; echo
生成されたトークンを HTTP 認可ベアラースキームの値として必ず使用してください。
このコマンドは次の応答を返します。
hello jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13
1.2.10. JVM モードでのアプリケーションの実行
アプリケーションを標準の Java アプリケーションとして実行できます。
アプリケーションをコンパイルします。
Quarkus CLI を使用:
quarkus build
Maven を使用:
./mvnw install
Gradle を使用:
./gradlew build
アプリケーションを実行します。
java -jar target/quarkus-app/quarkus-run.jar
1.2.11. ネイティブモードでのアプリケーションの実行
同じデモを、変更せずにネイティブモードにコンパイルできます。つまり、実稼働環境に JVM をインストールする必要がなくなります。ランタイムテクノロジーは生成されたバイナリーに組み込まれ、最小限のリソースで実行できるように最適化されています。
コンパイルには少し時間がかかるため、この手順はデフォルトで無効になっています。
native
プロファイルを有効にしてアプリケーションを再度ビルドします。Quarkus CLI を使用:
quarkus build --native
Maven を使用:
./mvnw install -Dnative
Gradle を使用:
./gradlew build -Dquarkus.native.enabled=true
次のバイナリーを直接実行します。
./target/security-jwt-quickstart-1.0.0-SNAPSHOT-runner
1.2.12. 解決策を調べる
security-jwt-quickstart
ディレクトリー リポジトリーには、このクイックスタートガイドで説明されているすべてのバージョンに加えて、挿入された JsonWebToken
トークンとそのクレームを CDI API 経由で使用するサブリソースを示す追加のエンドポイントが含まれています。
SmallRye JWT 拡張機能の機能について詳しく知るには、security-jwt-quickstart
ディレクトリーを調べてクイックスタートソリューションを確認することを推奨します。
1.3. リファレンスガイド
1.3.1. サポート対象のインジェクションスコープ
@ApplicationScoped
、@Singleton
、および @RequestScoped
outer Bean インジェクションスコープは、org.eclipse.microprofile.jwt.JsonWebToken
を注入するとすべてサポートされ、現在のトークンが表されるように JsonWebToken
用の @RequestScoped
スコープが適用されます。
ただし、個々のトークンクレームが String
などの単純型として注入される場合は、@RequestScoped
を使用する必要があります。以下に例を示します。
package org.acme.security.jwt; import jakarta.inject.Inject; import org.eclipse.microprofile.jwt.Claim; import org.eclipse.microprofile.jwt.Claims; @Path("/secured") @RequestScoped public class TokenSecuredResource { @Inject @Claim(standard = Claims.birthdate) String birthdate; }
挿入された JsonWebToken を
使用して個々のクレームにアクセスすることもできますが、この場合は @RequestScoped
を設定する必要はありません。
詳細は、MP JWT CDI Injection Requirements を参照してください。
1.3.2. サポート対象の公開鍵形式
公開鍵は、優先順に指定された以下のいずれかの形式でフォーマットできます。
- Public Key Cryptography Standards #8 (PKCS#8) PEM
- JSON Web Key (JWK)
- JSON Web Key Set (JWKS)
- JSON Web Key (JWK) Base64 URL でエンコードされている
- JSON Web Key Set (JWKS) Base64 URL でエンコードされている
1.3.3. 検証キーの取り扱い
非対称 RSA または Elliptic Curve (EC) 鍵を使用してトークンの署名を検証する必要がある場合は、mp.jwt.verify.publickey.location
プロパティーを使用してローカルまたはリモート検証鍵を参照します。
たとえば、EC 鍵を使用する場合は ES256
に設定し、mp.jwt.verify.publickey.algorithm
を使用して検証アルゴリズム (デフォルトは RS256
) をカスタマイズします。
対称シークレット鍵を使用してトークン署名を検証する必要がある場合は、JSON Web Key
(JWK) または JSON Web Key Set
(JWK Set) 形式のいずれかを使用して、このシークレット鍵を表す必要があります。次に例を示します。
{ "keys": [ { "kty":"oct", "kid":"secretKey", "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" } ] }
この秘密鍵 JWK は、smallrye.jwt.verify.key.location
でも参照する必要があります。smallrye.jwt.verify.algorithm
は、HS256
/HS384
/HS512
に設定する必要があります。
1.3.4. JWTParser を使用した JsonWebToken の解析および検証
JWT トークンを注入できない場合 (たとえば、サービスリクエストのペイロードに埋め込まれている場合や、サービスエンドポイントが外部手段でそれを取得する場合)、JWTParser
を使用できます。
import org.eclipse.microprofile.jwt.JsonWebToken; import io.smallrye.jwt.auth.principal.JWTParser; ... @Inject JWTParser parser; String token = getTokenFromOidcServer(); // Parse and verify the token JsonWebToken jwt = parser.parse(token);
また、これを使用して、トークンの検証または復号化の方法をカスタマイズすることもできます。たとえば、ローカルの SecretKey
を指定できます。
package org.acme.security.jwt; import io.smallrye.jwt.auth.principal.ParseException; import jakarta.inject.Inject; import jakarta.ws.rs.CookieParam; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.NewCookie; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.jwt.JsonWebToken; import io.smallrye.jwt.auth.principal.JWTParser; import io.smallrye.jwt.build.Jwt; @Path("/secured") public class SecuredResource { private static final String SECRET = "AyM1SysPpbyDfgZld3umj1qzKObwVMko"; @Inject JWTParser parser; @GET @Produces("text/plain") public Response getUserName(@CookieParam("jwt") String jwtCookie) throws ParseException { if (jwtCookie == null) { // Create a JWT token signed by using the 'HS256' algorithm String newJwtCookie = Jwt.upn("Alice").signWithSecret(SECRET); // or create a JWT token encrypted by using the 'A256KW' algorithm // Jwt.upn("alice").encryptWithSecret(secret); return Response.ok("Alice").cookie(new NewCookie("jwt", newJwtCookie)).build(); } else { // All mp.jwt and smallrye.jwt properties are still effective; only the verification key is customized. JsonWebToken jwt = parser.verify(jwtCookie, SECRET); // or jwt = parser.decrypt(jwtCookie, secret); return Response.ok(jwt.getName()).build(); } } }
quarkus-smallrye-jwt
が提供する HTTP
サポートなしで JWTParser
を使用する方法に関する SmallRye JWT を直接追加する方法 のセクションを参照してください。
1.3.5. トークンの復号化
アプリケーションで暗号化されたクレームまたは暗号化された内部署名クレームを含むトークンを受け入れる必要がある場合は、smallrye.jwt.decrypt.key.location
プロパティーを復号化キーを指すように設定するだけです。
これが唯一のキープロパティーセットである場合、受信トークンには暗号化されたクレームのみが含まれることが予想されます。mp.jwt.verify.publickey
または mp.jwt.verify.publickey.location
検証プロパティーのいずれかも設定されている場合、受信トークンには暗号化された内部署名トークンが含まれることが想定されます。
SmallRye JWT を使用した JWT トークンの生成 を参照し、暗号化トークンや内部署名後に暗号化されたトークンを迅速に生成する方法を学んでください。
1.3.6. カスタムファクトリー
io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipalFactory
は、JWT トークンを解析および検証し、JsonWebToken
プリンシパルに変換するために使用されるデフォルトの実装です。このファクトリーは、設定
セクションで説明されているように、MP JWT
と smallrye-jwt
プロパティーに依存して、JWT トークンを検証およびカスタマイズします。
ファイアウォールによってすでに検証されているトークンの再検証をスキップするなど、カスタムファクトリーを実装する必要がある場合は、次のいずれかの方法で実装できます。
-
META-INF/services/io.smallrye.jwt.auth.principal.JWTCallerPrincipalFactory
リソースを作成して、ServiceLoader
メカニズムを使用します。 -
以下の例のように、
代替
CDI Bean 実装を提供します。
import java.nio.charset.StandardCharsets; import java.util.Base64; import jakarta.annotation.Priority; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Alternative; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.consumer.InvalidJwtException; import io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipal; import io.smallrye.jwt.auth.principal.JWTAuthContextInfo; import io.smallrye.jwt.auth.principal.JWTCallerPrincipal; import io.smallrye.jwt.auth.principal.JWTCallerPrincipalFactory; import io.smallrye.jwt.auth.principal.ParseException; @ApplicationScoped @Alternative @Priority(1) public class TestJWTCallerPrincipalFactory extends JWTCallerPrincipalFactory { @Override public JWTCallerPrincipal parse(String token, JWTAuthContextInfo authContextInfo) throws ParseException { try { // Token has already been verified; parse the token claims only String json = new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), StandardCharsets.UTF_8); return new DefaultJWTCallerPrincipal(JwtClaims.parse(json)); } catch (InvalidJwtException ex) { throw new ParseException(ex.getMessage()); } } }
1.3.7. 呼び出しのブロック
quarkus-smallrye-jwt
エクステンションは、現在リアクティブでない SmallRye JWT ライブラリーを使用します。
これは、quarkus-smallrye-jwt
がリアクティブ Quarkus セキュリティーアーキテクチャーの一部として動作する観点から見ると、SmallRye JWT の検証または復号化コードに入る IO スレッドが、以下のいずれかの場合にブロックする可能性があることを意味します。
-
デフォルトのキーリゾルバーは、キーを含む
JsonWebKey
セットを更新します。これには、OIDC エンドポイントへのリモート呼び出しが含まれます。 -
AWS Application Load Balancer
(ALB
) キーリゾルバーなどのカスタムキーリゾルバーは、現在のトークンの鍵識別子ヘッダー値を使用して、キーを AWS ALB キーエンドポイントに対して解決します
このような場合、接続が遅い場合 (たとえば、キーエンドポイントへの応答に 3 秒以上かかる場合)、現在のイベントループスレッドがブロックされる可能性があります。
ブロックされないようにするには、quarkus.smallrye-jwt.blocking-authentication=true を
設定します。
1.3.8. トークンの伝播
ダウンストリームサービスへの Bearer アクセストークンの伝播は、トークン伝播 セクションを参照してください。
1.3.9. テスト
1.3.9.1. Wiremock
mp.jwt.verify.publickey.location
を HTTPS または HTTP ベースの JsonWebKey (JWK) セットを指すように設定した場合は、OpenID Connect Bearer Token インテグレーションテスト の Wiremock
セクションで説明されているものと同じアプローチを使用できます。ただし、MP JWT 設定プロパティーを代わりに使用するように application.properties
のみを変更します。
# keycloak.url is set by OidcWiremockTestResource mp.jwt.verify.publickey.location=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
1.3.9.2. Keycloak
Keycloak を使用し、mp.jwt.verify.publickey.location
を HTTPS または HTTP ベースの JsonWebKey (JWK) セットを指すように設定した場合は、OpenID Connect Bearer Token インテグレーションテスト の Keycloak セクションで説明されているものと同じアプローチを使用できます。ただし、MP JWT 設定プロパティーを代わりに使用するように application.properties
のみを変更します。
# keycloak.url is set by DevServices for Keycloak mp.jwt.verify.publickey.location=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus
Keycloak によって発行されたトークンには、iss
(issuer) クレームがレルムエンドポイントアドレスに設定されていることに注意してください。
Quarkus アプリケーションが Docker コンテナー内で実行される場合、DevServices for Keycloak によって起動された Keycloak コンテナーとネットワークインターフェイスを共有する可能性があります。このシナリオでは、Quarkus アプリケーションと Keycloak は内部の共有 Docker ネットワークを介して通信します。
このような場合は、代わりに以下の設定を使用してください。
# keycloak.url is set by DevServices for Keycloak, # Quarkus accesses it through an internal shared docker network interface. mp.jwt.verify.publickey.location=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs # Issuer is set to the docker bridge localhost endpoint address represented by the `client.quarkus.oidc.auth-server-url` property mp.jwt.verify.issuer=${client.quarkus.oidc.auth-server-url}
1.3.9.3. ローカル公開鍵
OpenID Connect Bearer Token インテグレーションテスト の Local Public Key
セクションで説明されているものと同じ方法を使用できますが、MP JWT 設定プロパティーを代わりに使用するよう application.properties
のみを変更します。
mp.jwt.verify.publickey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEqFyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwRTYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5eUF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYnsIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9xnQIDAQAB # set it to the issuer value which is used to generate the tokens mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus # required to sign the tokens smallrye.jwt.sign.key.location=privateKey.pem
1.3.9.4. TestSecurity アノテーション
次の依存関係を追加します。
Maven を使用:
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-test-security-jwt</artifactId> <scope>test</scope> </dependency>
Gradle を使用:
testImplementation("io.quarkus:quarkus-test-security-jwt")
次に、次のようなテストコードを記述します。
import static org.hamcrest.Matchers.is; import org.junit.jupiter.api.Test; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.security.TestSecurity; import io.quarkus.test.security.jwt.Claim; import io.quarkus.test.security.jwt.JwtSecurity; import io.restassured.RestAssured; @QuarkusTest @TestHTTPEndpoint(ProtectedResource.class) public class TestSecurityAuthTest { @Test @TestSecurity(user = "userJwt", roles = "viewer") public void testJwt() { RestAssured.when().get("test-security-jwt").then() .body(is("userJwt:viewer")); } @Test @TestSecurity(user = "userJwt", roles = "viewer") @JwtSecurity(claims = { @Claim(key = "email", value = "user@gmail.com") }) public void testJwtWithClaims() { RestAssured.when().get("test-security-jwt-claims").then() .body(is("userJwt:viewer:user@gmail.com")); } }
ここで、ProtectedResource
クラスは次のようになります。
@Path("/web-app") @Authenticated public class ProtectedResource { @Inject JsonWebToken accessToken; @GET @Path("test-security-jwt") public String testSecurityOidc() { return accessToken.getName() + ":" + accessToken.getGroups().iterator().next(); } @GET @Path("test-security-jwt-claims") public String testSecurityOidcUserInfoMetadata() { return accessToken.getName() + ":" + accessToken.getGroups().iterator().next() + ":" + accessToken.getClaim("email"); } }
@TestSecurity
アノテーションを常に使用する必要があり、その user
プロパティーは JsonWebToken.getName()
として返され、roles
プロパティーは JsonWebToken.getGroups()
として返される点に注意してください。@JwtSecurity
アノテーションは任意で、追加のトークンクレームの設定に使用可能です。
@TestSecurity
と @JwtSecurity
は、以下のようにメタアノテーションに統合できます。
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) @TestSecurity(user = "userOidc", roles = "viewer") @OidcSecurity(introspectionRequired = true, introspection = { @TokenIntrospection(key = "email", value = "user@gmail.com") } ) public @interface TestSecurityMetaAnnotation { }
これは、同じセキュリティー設定のセットを複数のテスト方法で使用する必要がある場合に特に便利です。
1.3.10. ログ内のエラーを確認する方法
トークンの検証または復号化エラーの詳細は、io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator
TRACE
レベルのロギングを有効にして確認してください。
quarkus.log.category."io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator".level=TRACE quarkus.log.category."io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator".min-level=TRACE
1.3.11. プロアクティブ認証
パブリックエンドポイントメソッドが呼び出されたときにトークンの検証をスキップする場合は、プロアクティブ認証 を無効にします。
トークンの検証が行われていない場合、挿入された JsonWebToken
にパブリックメソッドを通じてアクセスすることはできないことに注意してください。
1.3.12. SmallRye JWT を直接追加する方法
JWTParser で JsonWebToken を解析および検証 するには、以下の状況で quarkus-smallrye-jwt
の代わりに smallrye-jwt
を直接使用します。
-
Quarkus GRPC
などのHTTP
をサポートしない Quarkus エクステンションを使用している。 -
エクステンション固有の
HTTP
を指定し、そのサポートはQuarkus AWS Lambda
などのquarkus-smallrye-jwt
およびVert.x HTTP
によって提供されるサポートと競合する。
smallrye-jwt
依存関係の追加から開始します。
Maven を使用:
<dependency> <groupId>io.smallrye</groupId> <artifactId>smallrye-jwt</artifactId> </dependency>
Gradle を使用:
implementation("io.smallrye:smallrye-jwt")
また、application.properties
を更新して、以下のように smallrye-jwt
によって提供されるすべての CDI プロデューサーが含まれるようにします。
quarkus.index-dependency.smallrye-jwt.group-id=io.smallrye quarkus.index-dependency.smallrye-jwt.artifact-id=smallrye-jwt
1.4. 設定の参照
1.4.1. Quarkus の設定
ビルド時に固定された設定プロパティー: その他の設定プロパティーはすべて実行時にオーバーライドできます。
設定プロパティー | 型 | デフォルト |
MP-JWT 設定オブジェクト
環境変数: | boolean |
|
SHA256withRSA 署名をサポートする
環境変数: | string |
|
リモート鍵の取得が時間のかかる操作になる可能性がある場合は、このプロパティーを有効化します。ローカル鍵を使用する場合は有効化しないでください。
環境変数: | boolean |
|
認証用の認証情報を含まないリクエストであっても、常に HTTP 401 チャレンジを作成します。JWT 認証メカニズムは、認証チャレンジが必要な場合に HTTP 401 を返します。ただし、インタラクティブな認証メカニズムのいずれかと一緒に使用される場合は、ブラウザーからアプリケーションにアクセスするユーザーに HTTP 401 を返す必要がない場合があります。必要な場合は、このプロパティーを 'true' に設定することで、JWT 認証メカニズムがこのような場合に課題を作成しないことをリクエストできます。
環境変数: | boolean |
|
1.4.2. MicroProfile JWT 設定
プロパティー名 | デフォルト | 説明 |
---|---|---|
|
|
|
|
|
Config プロパティーを使用すると、公開鍵の外部または内部の場所を指定できます。値は相対パスまたは URL にすることができます。値が HTTPS ベースの JWK セットを指している場合、ネイティブモードで動作させるには、 |
|
|
署名アルゴリズムのリスト。Elliptic Curve 署名アルゴリズムをサポートするには、 |
|
| Config プロパティーでは、秘密復号化キーの外部または内部の場所を指定できます。 |
|
|
復号化アルゴリズムのリスト。SHA |
|
|
Config プロパティーは、サーバーが有効として受け入れる JWT の |
|
|
トークン |
|
| トークンの有効期限と経過時間の検証中に使用されるクロックスキュー (秒単位)。現在の時間が、トークンの有効期限後にこのプロパティーで指定された秒数内にある場合、期限切れのトークンが受け入れられます。デフォルト値は、60 秒です。 |
|
|
トークンの |
|
|
|
|
|
トークンが含まれるクッキーの名前。このプロパティーは、 |
1.4.3. 追加の SmallRye JWT 設定
SmallRye JWT は、トークン処理をカスタマイズするために使用できる追加のプロパティーを提供します。
プロパティー名 | デフォルト | 説明 |
---|---|---|
|
| 秘密鍵は文字列として提供されます。 |
|
| 公開鍵とシークレット鍵の両方を指すことができる検証鍵のロケーション。シークレット鍵は JWK 形式のみです。このプロパティーが設定されている場合、mp.jwt.verify.publickey.location は無視されることに注意してください。 |
|
署名アルゴリズム。このプロパティーは、 | |
|
|
このプロパティーを |
|
|
デフォルトでは、PEM、JWK、または JWK 鍵セットはローカルファイルシステムから読み取るか、MicroProfile JWT 仕様で必要に応じて URI から取得できます。AWS アプリケーションロードバランサー検証鍵の解決をサポートするには、このプロパティーを |
|
|
検証キーの検証を緩和します。このプロパティーを |
|
| このプロパティーが有効な場合、署名済みトークンには 'x5t' または 'x5t#S256' X509Certificate サムプリントヘッダーのいずれかが含まれている必要があります。検証キーは、JWK または PEM 証明書キー形式のみで使用できます。JWK 鍵には、'x5c' (Base64 でエンコードされた X509Certificate) プロパティーが設定されている必要があります。 |
|
|
|
|
|
キーキャッシュサイズ。このプロパティーおよび |
|
|
キーキャッシュエントリーの有効期間 (分単位)。このプロパティーおよび |
|
|
トークンが含まれるクッキーの名前。このプロパティーは、 |
|
|
|
|
|
|
|
|
鍵識別子。検証 JWK キーとすべての JWT トークンには、設定されている場合は一致する |
|
| JWT を発行して使用できる最大秒数。実質的に、JWT の有効期限と発行日の差は、この値を超えることはできません。このプロパティーを正でない値に設定すると、トークンが有効な 'iat' (issued at) クレームを持つ要件が緩和されます。 |
|
|
アプリケーションが、名前を返す |
|
|
サブジェクト名が含まれるクレームへのパス。これは最上位の JSON オブジェクトから始まり、各セグメントが JSON オブジェクト名のみを表す複数のセグメント (例: |
|
|
このプロパティーでは、現在のトークンに使用可能な標準またはカスタムの |
|
|
グループを含むクレームへのパス。トップレベルの JSON オブジェクトから開始し、各セグメントが JSON オブジェクト名のみを表す複数のセグメントを含めることができます (例: |
|
|
複数のグループ値を含む可能性のある文字列を分割するための区切り文字。これは、 |
|
| このプロパティーでは、現在のトークンに標準またはカスタムのグループ要求がない場合に、デフォルトのグループ要求値を設定できます。 |
|
|
JWK キャッシュの更新間隔 (分単位)。 |
|
|
強制される JWK キャッシュの更新間隔 (分単位)。これは、現在のトークンの |
|
|
有効期限の猶予時間 (秒単位)。デフォルトでは、現在の時刻がトークンの有効期限から 1 分以内であれば、期限切れのトークンも引き続き受け入れられます。このプロパティーは非推奨にされています。代わりに |
|
|
トークン |
|
| トークンに含める必要があるクレームのコンマ区切りリスト。 |
|
|
秘密復号キーの外部または内部の場所を指定するための設定プロパティー。このプロパティーは非推奨となりました。 |
|
| 復号化アルゴリズム。 |
|
| 復号化鍵は文字列として指定されます。 |
|
|
復号化鍵の識別子。これが設定されている場合には、復号化 JWK 鍵とすべての JWT トークンに一致する |
|
|
鍵を |
|
|
すべてのホスト名を信頼します。鍵を |
|
|
信頼できるホスト名のセット。鍵を |
|
| HTTP プロキシーホスト。 |
|
| HTTP プロキシーポート。 |
|
|
このプロパティーは、 |
|
このプロパティーは、 | |
|
キーストアパスワード | |
|
このプロパティーは、 | |
|
| |
|
| |
|
| アプリケーションの起動時にリモート鍵を解決するには、このプロパティーを true に設定します。 |
1.5. 参考資料
第2章 JSON Web Token の構築、署名、および暗号化
JSON Web Token (JWT) は 、RFC 7519 仕様で、クレームを表すコンパクトで URL セーフな手段として定義されています。これらのクレームは JSON オブジェクトとしてエンコードされ、JSON Web 署名 (JWS) 構造のペイロードまたは JSON Web 暗号化 (JWE) 構造のプレーンテキストとして使用できます。このメカニズムにより、クレームをデジタル署名したり、メッセージ認証コード (MAC) を使用して整合性を保護したり、暗号化したりできるようになります。
クレームに署名することが、クレームを確保するための最も一般的な方法です。通常、JWT トークンは、JSON Web Signature (JWS) 仕様で概説されている手順に従って、JSON としてフォーマットされたクレームに署名することによって生成されます。
クレームに機密情報が含まれている場合、JSON Web Encryption (JWE) 仕様を使用して機密性を確保できます。このアプローチでは、暗号化されたクレームを含む JWT が生成されます。
セキュリティーを強化するために、両方の方法を組み合わせることができます。まずクレームに署名し、次に結果のネストされた JWT を暗号化します。このプロセスにより、請求の機密性と完全性の両方が保証されます。
SmallRye JWT Build API は、これらすべてのオプションをサポートすることで、JWT クレームのセキュリティー保護を簡素化します。この機能を提供するために、内部では Jose4J ライブラリーを使用します。
2.1. 依存関係
SmallRye JWT ビルド API を使用するには、プロジェクトに次の依存関係を追加します。
Maven を使用:
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-jwt-build</artifactId> </dependency>
Gradle を使用:
implementation("io.quarkus:quarkus-smallrye-jwt-build")
quarkus-smallrye-jwt
拡張機能でサポートされている MicroProfile JWT エンドポイントを作成せずに、SmallRye JWT ビルド API を独立して使用できます。
2.2. 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
(有効期限): 現在の時刻から 5 分後 (smallrye.jwt.new-token.lifespan
プロパティーでカスタマイズ可能) -
jti
(一意のトークン識別子)
ビルダーで直接設定しなくても済むように、次のプロパティーをグローバルに設定できます。
-
smallrye.jwt.new-token.issuer
: デフォルトの発行者を指定します。 -
smallrye.jwt.new-token.audience
: デフォルトのオーディエンスを指定します。
クレームを初期化して設定した後、次のステップはクレームを保護する方法を決定することです。
2.3. クレームの署名
クレームにはすぐに署名することも、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();
署名
ステップと 暗号化 ステップを組み合わせて、内部署名および暗号化された
トークンを作成できます。詳細は、クレームに署名し、ネストされた JWT トークンを暗号化する セクションを参照してください。
2.4. クレームの暗号化
クレームは、署名方法と同様に、すぐに暗号化することも、JSON Web Encryption (JWE)
ヘッダーを設定した後に暗号化することもできます。ただし、API は署名および内部署名操作をサポートするように最適化されているため、クレームを暗号化するには、常に JwtEncryptionBuilder
への jwe()
遷移が必要です。
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 公開鍵を使用してトークンを暗号化するには、署名鍵が使用可能な場合は最初にトークンに署名することを推奨します。詳細は、クレームに署名し、ネストされた JWT トークンを暗号化する セクションを参照してください。
2.5. クレームに署名し、ネストされた 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();
2.6. JWT の高速生成
smallrye.jwt.sign.key.location
または smallrye.jwt.encrypt.key.location
プロパティーが設定されている場合は、リソース、マップ、JsonObjects などの既存のクレームを 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
(対象ユーザー) のクレームは、まだ設定されていない場合は自動的に追加されます。
2.7. 鍵の処理
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
プロパティーを使用します。
キーは、トークンを構築するコードによってロードされ、トークン作成のために 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
プロパティーを使用してキーを参照できます。
JWK の例
{ "kty":"oct", "kid":"secretKey", "k":"Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I" }
JWK セットの例
{ "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 セット
、または PEM
形式で保存できます。
2.8. SmallRye JWT ビルダーの設定
SmallRye JWT は次のプロパティーをサポートしており、これを使用してクレームの署名または暗号化の方法をカスタマイズできます。
プロパティー名 | デフォルト | 説明 |
---|---|---|
|
|
引数なしの |
|
|
引数なしの |
|
| 署名キー識別子。JWK キーが使用される場合にのみチェックされます。 |
|
|
引数なしの |
| False | 署名鍵の検証を緩和します。 |
|
|
引数なしの |
|
| 暗号鍵識別子。JWK キーが使用される場合にのみチェックされます。 |
| False | 暗号化鍵の検証を緩和します。 |
|
| 署名アルゴリズム。JWT 署名ビルダーが署名アルゴリズムをまだ設定していないかどうかを確認します。 |
|
| 鍵暗号化アルゴリズム。JWT 暗号化ビルダーがキー暗号化アルゴリズムをまだ設定していないかどうかを確認します。 |
|
| コンテンツ暗号化アルゴリズム。JWT 暗号化ビルダーがコンテンツ暗号化アルゴリズムをまだ設定していないかどうかを確認します。 |
|
|
このクレームがまだ設定されていない場合に、 |
|
|
このクレームがまだ設定されていない場合に、 |
|
|
このクレームがまだ設定されていない場合に、 |
| False |
このプロパティーの |
|
|
This property can be used to customize a keystore type if either |
|
このプロパティーは、 | |
|
キーストアパスワード | |
|
| |
|
| |
|
|