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 を検証する方法、JWT を MicroProfile JWT org.eclipse.microprofile.jwt.JsonWebToken として表現する方法、ベアラートークン認可と ロールベースのアクセス制御 を使用して Quarkus HTTP エンドポイントを保護する方法を説明します。
Quarkus OpenID Connect (quarkus-oidc) エクステンションはベアラートークン認可もサポートし、smallrye-jwt を使用してベアラートークンを JsonWebToken として表現します。詳細は、OIDC Bearer Token Authentication ガイドを参照してください。
Quarkus アプリケーションで OIDC Authorization Code Flow を使用してユーザーを認証する必要がある場合は、OpenID Connect エクステンションを使用する必要があります。詳細は、Web アプリケーションを保護するための OIDC Code Flow Mechanism を参照してください。
1.1. 前提条件 リンクのコピーリンクがクリップボードにコピーされました!
このガイドを完了するには、以下が必要です。
- 約 15 分
- IDE
-
JAVA_HOMEが適切に設定された状態でインストールされた JDK 17 以降 - Apache Maven 3.8.6 以降
- オプション: Quarkus CLI (使用する場合)
- オプション: ネイティブ実行可能ファイルをビルドする場合は、インストールおよび 適切に設定された Mandrel または GraalVM (ネイティブコンテナービルドを使用する場合は Docker)。
1.2. Quickstart リンクのコピーリンクがクリップボードにコピーされました!
1.2.1. ソリューション リンクのコピーリンクがクリップボードにコピーされました!
アプリケーションを段階的に作成するには、次のセクションの指示に従うことを推奨します。必要に応じて、完成した例に進むこともできます。
例を利用するには、Git リポジトリーをクローンするか、アーカイブをダウンロードします。
-
リポジトリーのクローンを作成します:
git clone https://github.com/quarkusio/quarkus-quickstarts.git -b 3.20 - archive をダウンロードします。
完成したソリューションは、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-quickstartGradle プロジェクトを作成するには、
--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-quickstartGradle プロジェクトを作成するには、
-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-buildMaven を使用する場合:
./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;
@GET
@Path("permit-all")
@PermitAll
@Produces(MediaType.TEXT_PLAIN)
public String hello(@Context SecurityContext ctx) {
return getResponseString(ctx);
}
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;
}
}
- 1
JsonWebTokenインターフェイスが注入され、現在の認証済みトークンに関連付けられたクレームへのアクセスが提供されます。このインターフェイスはjava.security.Principalを拡張します。- 2
@PermitAllは標準の Jakarta セキュリティーアノテーションです。これは、認証されているかどうかに関係なく、指定されたエンドポイントがすべての呼び出し元からアクセスできることを示します。- 3
- リクエストのセキュリティー状態を検査するために、Jakarta REST
SecurityContextが注入されます。getResponseString()関数は応答を生成します。 - 4
- リクエストのユーザー/呼び出し元の
Principalを null と照合して、呼び出しが安全でないかどうかを確認します。 - 5
JsonWebTokenは現在のPrincipalを表すため、PrincipalとJsonWebTokenの名前が一致することを確認します。- 6
Principalの名前を取得します。- 7
- 呼び出し元の名前、リクエスト
SecurityContextのisSecure()とgetAuthenticationScheme()の状態、および null 以外のJsonWebTokenが注入されたかどうかを含む応答をビルドします。
1.2.4. 開発モードでのアプリケーションの実行 リンクのコピーリンクがクリップボードにコピーされました!
これで、次のいずれかのコマンドを使用して、アプリケーションを開発モードで実行する準備が整いました。
Quarkus CLI を使用する場合:
quarkus devMaven を使用する場合:
./mvnw quarkus:devGradle を使用する場合:
./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 が提供されていないため、エンドポイントでセキュリティー状態が確認されることはなく、応答もそれと一致しています。
-
usernameは匿名です。 -
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;
@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();
}
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 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 Unauthorized エラーが発生しました。
エンドポイントにアクセスするには、有効な JWT を取得してリクエストに含める必要があります。これには 2 つのステップが含まれます。
- JWT を検証するために必要な情報を使用して SmallRye JWT エクステンションを設定します。
- 設定に一致する適切なクレームを含む JWT を生成する。
1.2.5. SmallRye JWT エクステンションのセキュリティー情報の設定 リンクのコピーリンクがクリップボードにコピーされました!
以下の内容で security-jwt-quickstart/src/main/resources/application.properties を作成します。
TokenSecuredResource のアプリケーションのプロパティー
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=https://example.com/issuer
quarkus.native.resources.includes=publicKey.pem
- 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")
.upn("jdoe@quarkus.io")
.groups(new HashSet<>(Arrays.asList("User", "Admin")))
.claim(Claims.birthdate.name(), "2001-07-13")
.sign();
System.out.println(token);
System.exit(0);
}
}
- 1
- JWT に
iss(発行者) クレームを設定します。トークンが有効と見なされるためには、この値がサーバー側のmp.jwt.verify.issuer設定と一致する必要があります。 - 2
upn(ユーザープリンシパル名) クレームを指定します。これは、MicroProfile JWT RBAC 仕様では、コンテナーセキュリティー API でPrincipalを識別するための推奨クレームとして定義されています。- 3
- JWT ベアラーに割り当てられたグループメンバーシップと最上位レベルのロールを提供する
groupsクレームを定義します。 - 4
birthdateクレームを追加します。これは機密情報とみなされる可能性があるため、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 Authorization Bearer スキームの値として必ず使用してください。
このコマンドは次の応答を返します。
hello jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13
正常に完了しました。現在、以下の状態になっています。
-
匿名でない呼び出し元名:
jdoe@quarkus.io -
認証スキーム:
Bearer -
null 以外の
JsonWebToken -
birthdateクレーム値
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
public class TokenSecuredResource {
@Inject
JsonWebToken jwt;
@Inject
@Claim(standard = Claims.birthdate)
String birthdate;
@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;
}
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 Authorization Bearer スキームの値として必ず使用してください。
このコマンドは次の応答を返します。
hello jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13
1.2.10. JVM モードでアプリケーションを実行する リンクのコピーリンクがクリップボードにコピーされました!
アプリケーションを標準の Java アプリケーションとして実行できます。
アプリケーションをコンパイルします。
Quarkus CLI を使用する場合:
quarkus buildMaven を使用する場合:
./mvnw installGradle を使用する場合:
./gradlew build
アプリケーションを実行します。
java -jar target/quarkus-app/quarkus-run.jar
1.2.11. ネイティブモードでのアプリケーションの実行 リンクのコピーリンクがクリップボードにコピーされました!
この同じデモを、変更せずにネイティブモードにコンパイルできます。つまり、実稼働環境に JVM をインストールする必要がなくなります。ランタイムテクノロジーは生成されたバイナリーに組み込まれ、最小限のリソースで実行できるように最適化されています。
コンパイルには少し時間がかかるため、この手順はデフォルトで無効になっています。
nativeプロファイルを有効にしてアプリケーションを再度ビルドします。Quarkus CLI を使用する場合:
quarkus build --nativeMaven を使用する場合:
./mvnw install -DnativeGradle を使用する場合:
./gradlew build -Dquarkus.native.enabled=true
次のバイナリーを直接実行します。
./target/security-jwt-quickstart-1.0.0-SNAPSHOT-runner
1.2.12. ソリューションの探索 リンクのコピーリンクがクリップボードにコピーされました!
security-jwt-quickstart ディレクトリー リポジトリーには、このクイックスタートガイドで扱うすべてのバージョンが含まれており、さらに CDI API を介して注入された JsonWebToken トークンとそのクレームを使用し、サブリソースの動作を実演する追加のエンドポイントも含まれています。
security-jwt-quickstart ディレクトリーでクイックスタートソリューションを確認し、SmallRye JWT エクステンションの機能に関する情報を確認することを推奨します。
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 または楕円曲線 (EC) 鍵を使用してトークンの署名を検証する必要がある場合は、mp.jwt.verify.publickey.location プロパティーを使用してローカルまたはリモートの検証鍵を参照します。
たとえば、EC 鍵を使用する場合は ES256 に設定し、mp.jwt.verify.publickey.algorithm を使用して検証アルゴリズム (デフォルトは RS256) をカスタマイズします。
対称シークレットキーを使用してトークン署名を検証する必要がある場合は、JSON Web Key (JWK) または JSON Web Key Set (JWK セット) 形式のいずれかを使用して、このシークレットキーを表す必要があります。以下はその例です。
{
"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 プリンシパルに変換するために使用されるデフォルトの実装です。このファクトリーは、Configuration セクションで説明されているように、MP JWT と smallrye-jwt プロパティーに依存して、JWT トークンを検証およびカスタマイズします。
ファイアウォールによってすでに検証されているトークンの再検証をスキップするなど、カスタムファクトリーを実装する必要がある場合は、次のいずれかの方法で実装できます。
-
META-INF/services/io.smallrye.jwt.auth.principal.JWTCallerPrincipalFactoryリソースを作成して、ServiceLoaderメカニズムを使用します。 -
以下の例のように、
AlternativeCDI 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 スレッドが、以下のいずれかの場合にブロックする可能性があることを意味します。
-
デフォルトのキーリゾルバーが、OIDC エンドポイントへのリモートコールを伴うキーを含む
JsonWebKeyセットを更新します -
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 設定 リンクのコピーリンクがクリップボードにコピーされました!
| プロパティー名 | デフォルト | 説明 |
|---|---|---|
|
|
|
|
|
|
|
設定プロパティーを使用すると、公開鍵の外部または内部の場所を指定できます。値は相対パスまたは URL にすることができます。値が HTTPS ベースの JWK セットを指している場合、ネイティブモードで動作させるには、 |
|
|
|
署名アルゴリズムのリスト。Elliptic Curve 署名アルゴリズムをサポートするには、 |
|
|
| Config プロパティーを使用すると、秘密復号化キーの外部または内部の場所を指定できます。 |
|
|
|
復号化アルゴリズムのリスト。SHA-256 のみの RSA-OAEP をサポートするには、 |
|
|
|
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 Signature (JWS) 構造のペイロードまたは JSON Web Encryption (JWE) 構造のプレーンテキストとして使用できます。このメカニズムにより、クレームをデジタル署名したり、Message Authentication Code (MAC) を使用して整合性を保護したり、暗号化したりできるようになります。
クレームに署名することが、クレームを確保するための最も一般的な方法です。通常、JWT トークンは、JSON Web Signature (JWS) 仕様で概説されている手順に従って、JSON としてフォーマットされたクレームに署名することによって生成されます。
クレームに機密情報が含まれている場合、JSON Web Encryption (JWE) 仕様を使用して機密性を確保できます。このアプローチでは、暗号化されたクレームを含む JWT が生成されます。
セキュリティーを強化するために、両方の方法を組み合わせることができます。最初にクレームへ署名し、その結果として生成されるネストされた JWT を次に暗号化します。このプロセスにより、クレームの機密性と整合性の両方が確保されます。
SmallRye JWT Build API は、これらすべてのオプションをサポートすることで、JWT クレームの保護を簡素化します。この機能を提供するために、内部では Jose4J ライブラリーを使用します。
2.1. 依存関係 リンクのコピーリンクがクリップボードにコピーされました!
SmallRye JWT Build API を使用するには、プロジェクトに次の依存関係を追加します。
Maven を使用する場合:
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-jwt-build</artifactId> </dependency>Gradle を使用する場合:
implementation("io.quarkus:quarkus-smallrye-jwt-build")
SmallRye JWT Build API は、quarkus-smallrye-jwt エクステンションがサポートする MicroProfile JWT エンドポイントを作成せずに、単独で使用できます。
2.2. 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: デフォルトのオーディエンスを指定します。
クレームを初期化して設定した後、次のステップとして、クレームを保護する方法を決定します。
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(アルゴリズム) ヘッダーはデフォルトで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 ステップと 暗号化 ステップを組み合わせて、inner-signed and encrypted トークンを作成できます。詳細は、クレームに署名し、ネストされた 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 プロパティーが設定されている場合は、リソース、マップ、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 (オーディエンス) などのクレームは、まだ設定されていない場合は自動的に追加されます。
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 Build 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 Set、または PEM 形式で保存できます。
2.8. SmallRye JWT ビルダーの設定 リンクのコピーリンクがクリップボードにコピーされました!
SmallRye JWT は、クレームの署名または暗号化の方法をカスタマイズするために使用できる次のプロパティーをサポートしています。
| プロパティー名 | デフォルト | 説明 |
|---|---|---|
|
|
|
引数なしの |
|
|
|
引数なしの |
|
|
| 署名キー識別子。JWK キーが使用される場合にのみチェックされます。 |
|
|
|
引数なしの |
|
|
| 署名鍵の検証を緩和します。 |
|
|
|
引数なしの |
|
|
| 暗号鍵識別子。JWK キーが使用される場合にのみチェックされます。 |
|
|
| 暗号化鍵の検証を緩和します。 |
|
|
| 署名アルゴリズム。JWT 署名ビルダーが署名アルゴリズムをまだ設定していないかが確認されます。 |
|
|
| 鍵暗号化アルゴリズム。JWT 暗号化ビルダーがキー暗号化アルゴリズムをまだ設定していないかが確認されます。 |
|
|
| コンテンツ暗号化アルゴリズム。JWT 暗号化ビルダーがコンテンツ暗号化アルゴリズムをまだ設定していないかが確認されます。 |
|
|
|
このクレームがまだ設定されていない場合に、 |
|
|
|
このクレームがまだ設定されていない場合に、 |
|
|
|
このクレームがまだ設定されていない場合に、 |
|
|
|
このプロパティーの |
|
|
|
このプロパティーを |
|
|
|
This property can be used to customize a keystore type if either |
|
|
このプロパティーは、 | |
|
|
キーストアパスワード | |
|
|
| |
|
|
このプロパティーは、 | |
|
|
このプロパティーは、 |