The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

JWT RBAC の使用

このガイドでは、Quarkusアプリケーションが SmallRye JWT を利用して、 JSON Web Token を検証し、MicroProfile JWT org.eclipse.microprofile.jwt.JsonWebToken として表現し、Bearer Token Authorizationと Role-Based Access Control を使用してQuarkus HTTPエンドポイントへのセキュアなアクセスを提供する方法について説明します。

Quarkus OpenId Connect quarkus-oidc エクステンションは、ベアラートークン認可もサポートしており、 smallrye-jwt を使用してベアラートークンを JsonWebToken として表現します。詳しくは OpenID Connect (OIDC) を使用した、ベアラートークン認可によるサービスアプリケーションの保護 ガイドをご覧ください。OpenId Connectエクステンションは、QuarkusアプリケーションがOIDC Authorization Code Flowを使用してユーザーを認証する必要がある場合に使用する必要があります。詳しくは OpenID Connect (OIDC) を使った認証コードフローによる WEB アプリケーションの保護 ガイドをご覧ください。

前提条件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.8.6

  • 使用したい場合は、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

クイックスタート

ソリューション

次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、すぐに完成した例に飛んでも構いません。

Git リポジトリーのクローンを作成: git clonehttps://github.com/quarkusio/quarkus-quickstarts.git、または アーカイブ をダウンロードします。

ソリューションは security-jwt-quickstart ディレクトリー にあります。

Maven プロジェクトの作成

まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。

コマンドラインインタフェース
quarkus create app org.acme:security-jwt-quickstart \
    --extension='resteasy-reactive-jackson,smallrye-jwt,smallrye-jwt-build' \
    --no-code
cd security-jwt-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストール方法や使用方法については、<a href="cli-tooling.html">Quarkus CLIガイド</a> を参照してください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.14.2.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=security-jwt-quickstart \
    -Dextensions='resteasy-reactive-jackson,smallrye-jwt,smallrye-jwt-build' \
    -DnoCode
cd security-jwt-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

このコマンドは、RESTエンドポイントを持つMavenプロジェクトを生成し、MicroProfile JWT RBAC サポートを含む smallrye-jwt エクステンションをインポートします。

すでにQuarkusプロジェクトが設定されている場合は、プロジェクトのベースディレクトリーで以下のコマンドを実行することで、プロジェクトに smallrye-jwt エクステンションを追加することができます。

コマンドラインインタフェース
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'

これにより、 pom.xml に以下が追加されます:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt-build</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-smallrye-jwt")
implementation("io.quarkus:quarkus-smallrye-jwt-build")

JAX-RS リソースの調査

RESTエンドポイントを src/main/java/org/acme/security/jwt/TokenSecuredResource.java に以下の内容で作成します。

REST エンドポイント V1
package org.acme.security.jwt;

import java.security.Principal;

import javax.annotation.security.PermitAll;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.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 ここでは、現在認証されているトークンに関連付けられたクレームへのアクセスを提供する java.security.Principal インターフェイスのエクステンションである JsonWebToken インターフェイスを挿入します。
2 @PermitAll は、JSR 250 の一般的なセキュリティーアノテーションであり、認証されているかどうかに関係なく、特定のエンドポイントにすべての呼び出し元がアクセスできることを示します。
3 ここでは、JAX-RS SecurityContext を挿入して呼び出しのセキュリティー状態を検査し、getResponseString() 関数を使用してレスポンス文字列を入力します。
4 ここでは、リクエストのユーザー/呼び出し元の Principal を null と照合して、呼び出しが安全でないかどうかを確認します。
5 ここでは、JsonWebToken が現在の Principal を表しているため、Principal と JsonWebToken が同じ名前であることを確認します。
6 ここで、Principal の名前を取得します。
7 構築した応答では、呼び出し元の名前、リクエスト SecurityContextisSecure()getAuthenticationScheme() の状態、null 以外の JsonWebToken が挿入されたかどうかを利用します。

アプリケーションの実行

これで、アプリケーションを実行する準備が整いました。以下を使用してください。

コマンドラインインタフェース
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, resteasy-reactive, resteasy-reactive-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を設定していませんので、エンドポイントから見たセキュリティーステートがあるとは期待できず、レスポンスもそれに沿ったものとなっています。

  • ユーザー名は anonymous (匿名) です。

  • isHttps は https を使用しないので false です。

  • authScheme は null です。

  • hasJWTは false です。

Ctrl-Cを使用してQuarkusサーバーを停止します。

では実際に何かをセキュア化してみましょう。下記の新しいエンドポイントメソッド helloRolesAllowed を見てみましょう。

REST エンドポイント V2
package org.acme.security.jwt;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;

import org.eclipse.microprofile.jwt.JsonWebToken;

@Path("/secured")
@RequestScoped
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;
    }
}
1 ここでは、JsonWebToken を挿入します。
2 この新しいエンドポイントは /secured/roles-allowed に存在します。
3 @RolesAllowed は、"User" または "Admin" ロールが割り当てられている場合は呼び出し元が特定のエンドポイントにアクセスできることを示す JSR 250 の一般的なセキュリティーアノテーションです。
4 ここでは、hello メソッドと同じ方法で応答を作成しますが、挿入された JsonWebToken を直接呼び出すことで JWT birthdate クレームの値を追加します。

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
Not authorized

素晴らしいですね。リクエストに JWT トークンを提供していないため、エンドポイントにはアクセスできないはずであり、実際にアクセスできませんでした。代わりに HTTP 401 Unauthorized エラーを受け取りました。エンドポイントにアクセスするためには、有効な JWT を取得して渡す必要があります。これには 2 つのステップがあります。1) SmallRye JWT エクステンションに JWT の検証方法に関する情報を設定し、2) 適切なクレームを持つ適合する JWT を生成します。

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 の場所を指すように公開鍵の場所を設定しています。この鍵はパート B 公開鍵の追加 で追加します。
2 発行者を URL 文字列 https://example.com/issuer に設定しています。
3 ネイティブ実行可能ファイルのリソースとして公開鍵を含めています。

公開鍵の追加

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-----

JWT の生成

多くの場合、 Keycloak などの ID マネージャーから 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;

public class GenerateToken {
    /**
     * Generate 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);
    }
}
1 iss クレームは JWT の発行者です。トークンが有効であると認められるためには、これがサーバー側の mp.jwt.verify.issuer と一致する必要があります。
2 upn クレームは、MicroProfile JWT RBAC 仕様で、コンテナーセキュリティー API を介して表示される Principal に使用する優先クレームとして定義されています。
3 group クレームは、JWT ベアラーに関連付けられたグループとトップレベルのロールを提供します。
4 birthday クレームです。機密性の高いクレームと見なすことができるため、クレームの暗号化を検討したくなるかもしれません。 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 コマンド
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 エンドポイントで使用する JWT を生成できます。これを行うには、次のコマンドを実行します。

JWT 生成出力のサンプル
$ mvn exec:java -Dexec.mainClass=org.acme.security.jwt.GenerateToken -Dexec.classpathScope=test -Dsmallrye.jwt.sign.key.location=privateKey.pem

eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjU5Njc2LCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY1OTk3NiwiaWF0IjoxNTUxNjU5Njc2LCJqdGkiOiJhLTEyMyJ9.O9tx_wNNS4qdpFhxeD1e7v4aBNWz1FCq0UV8qmXd7dW9xM4hA5TO-ZREk3ApMrL7_rnX8z81qGPIo_R8IfHDyNaI1SLD56gVX-NaOLS2OjfcbO3zOWJPKR_BoZkYACtMoqlWgIwIRC-wJKUJU025dHZiNL0FWO4PjwuCz8hpZYXIuRscfFhXKrDX1fh3jDhTsOEFfu67ACd85f3BdX9pe-ayKSVLh_RSbTbBPeyoYPE59FW7H5-i8IE-Gqu838Hz0i38ksEJFI25eR-AJ6_PSUD0_-TV3NjXhF3bFIeT4VSaIZcpibekoJg0cQm-4ApPEcPLdgTejYHA-mupb8hSwg

JWT 文字列は Base64 URL でエンコードされた文字列で、'.' で区切られた 3 つのパートで構成されています。最初のパートは JWT ヘッダー、2 つ目のパートは JWT クレーム、3 つ目のパートは JWT シグネチャーです。

/secured/roles-allowed へのセキュリティーで保護されたアクセス

これを使用して、/secured/roles-allowed エンドポイントにセキュリティーで保護されたリクエストを作成しましょう。Quarkus サーバーが引き続き開発モードで実行されていることを確認してから、次のコマンドを実行し、前の手順で生成された自分の JWT バージョンを使用していることを確認します。

curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjUyMDkxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY1MjM5MSwiaWF0IjoxNTUxNjUyMDkxLCJqdGkiOiJhLTEyMyJ9.aPA4Rlc4kw7n_OZZRRk25xZydJy_J_3BRR8ryYLyHTO1o68_aNWWQCgpnAuOW64svPhPnLYYnQzK-l2vHX34B64JySyBD4y_vRObGmdwH_SEufBAWZV7mkG3Y4mTKT3_4EWNu4VH92IhdnkGI4GJB6yHAEzlQI6EdSOa4Nq8Gp4uPGqHsUZTJrA3uIW0TbNshFBm47-oVM3ZUrBz57JKtr0e9jv0HjPQWyvbzx1HuxZd6eA8ow8xzvooKXFxoSFCMnxotd3wagvYQ9ysBa89bgzL-lhjWtusuMFDUVYwFqADE7oOSOD4Vtclgq8svznBQ-YpfTHfb9QEcofMlpyjNA" http://127.0.0.1:8080/secured/roles-allowed; echo
JWT を含む /secured/roles-allowed の curl コマンド
$ curl -H "Authorization: Bearer eyJraWQ..." http://127.0.0.1:8080/secured/roles-allowed; echo
hello + jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13

成功! これで、以下が得られます

  • jdoe@quarkus.io という非匿名の発信者名

  • ベアラーの認証スキーム

  • 非 null の JsonWebToken

  • birthdate クレームの値

JsonWebToken とクレーム挿入の使用

保護された REST エンドポイントにアクセスするための JWT を生成できるようになったので、JsonWebToken インターフェイスと JWT クレームでさらに何ができるかを見てみましょう。org.eclipse.microprofile.jwt.JsonWebToken インターフェイスは java.security.Principal インターフェイスを拡張します。実際、これは 前に使用した javax.ws.rs.core.SecurityContext#getUserPrincipal() 呼び出しにより返されるオブジェクトのタイプです。これは、CDI は使用しないが、REST コンテナー SecurityContext にアクセスできるコードは、 SecurityContext#getUserPrincipal() をキャストすることで、呼び出し元の JsonWebToken インターフェイスを取得できることを意味します。

JsonWebToken インターフェイスは、基盤となる JWT のクレームにアクセスするためのメソッドを定義します。これは、JWT に存在する可能性のある MicroProfile JWT RBAC 仕様および任意のクレームで必要な一般的クレームのアクセッサを提供します。。

すべての JWT クレームを挿入することもできます。TokenSecuredResource を別のエンドポイント /secured/roles-allowed-admin で拡張してみましょう。これは、(JsonWebToken から取得するのではなく) 挿入された birthdate クレームを使用します。

package org.acme.security.jwt;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.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; (1)
    @Inject
    @Claim(standard = Claims.birthdate)
    String birthdate; (2)

    @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; (3)
    }

    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 を挿入します。
2 ここでは、birthday クレームを String として挿入します。これが @RequestScoped スコープが必要になった理由です。
3 ここでは、挿入された birthday クレームを使用して、最終的な応答を作成します。

次に、トークンを再度生成して実行します。

curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjUyMDkxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY1MjM5MSwiaWF0IjoxNTUxNjUyMDkxLCJqdGkiOiJhLTEyMyJ9.aPA4Rlc4kw7n_OZZRRk25xZydJy_J_3BRR8ryYLyHTO1o68_aNWWQCgpnAuOW64svPhPnLYYnQzK-l2vHX34B64JySyBD4y_vRObGmdwH_SEufBAWZV7mkG3Y4mTKT3_4EWNu4VH92IhdnkGI4GJB6yHAEzlQI6EdSOa4Nq8Gp4uPGqHsUZTJrA3uIW0TbNshFBm47-oVM3ZUrBz57JKtr0e9jv0HjPQWyvbzx1HuxZd6eA8ow8xzvooKXFxoSFCMnxotd3wagvYQ9ysBa89bgzL-lhjWtusuMFDUVYwFqADE7oOSOD4Vtclgq8svznBQ-YpfTHfb9QEcofMlpyjNA" http://127.0.0.1:8080/secured/roles-allowed-admin; echo
$ curl -H "Authorization: Bearer eyJraWQ..." http://127.0.0.1:8080/secured/roles-allowed-admin; echo
hello + jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13

アプリケーションのパッケージ化と実行

いつものように、アプリケーションは以下の方法でパッケージ化されます。

コマンドラインインタフェース
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

そして、java -jar target/quarkus-app/quarkus-run.jar を使って実行されます。

ランナー jar の例
$ java -jar target/quarkus-app/quarkus-run.jar
2019-03-28 14:27:48,839 INFO  [io.quarkus] (main) Quarkus 2.14.2.Final started in 0.796s. Listening on: http://[::]:8080
2019-03-28 14:27:48,841 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy-reactive, resteasy-reactive-jackson, security, smallrye-jwt]

次のようにネイティブ実行可能ファイルを生成することもできます。

コマンドラインインタフェース
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native
ネイティブ実行可能ファイル例
[INFO] Scanning for projects...
...
[security-jwt-quickstart-runner:25602]     universe:     493.17 ms
[security-jwt-quickstart-runner:25602]      (parse):     660.41 ms
[security-jwt-quickstart-runner:25602]     (inline):   1,431.10 ms
[security-jwt-quickstart-runner:25602]    (compile):   7,301.78 ms
[security-jwt-quickstart-runner:25602]      compile:  10,542.16 ms
[security-jwt-quickstart-runner:25602]        image:   2,797.62 ms
[security-jwt-quickstart-runner:25602]        write:     988.24 ms
[security-jwt-quickstart-runner:25602]      [total]:  43,778.16 ms
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  51.500 s
[INFO] Finished at: 2019-03-28T14:30:56-07:00
[INFO] ------------------------------------------------------------------------

$ ./target/security-jwt-quickstart-runner
2019-03-28 14:31:37,315 INFO  [io.quarkus] (main) Quarkus 0.12.0 started in 0.006s. Listening on: http://[::]:8080
2019-03-28 14:31:37,316 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy-reactive, resteasy-reactive-jackson, security, smallrye-jwt]

ソリューションの探索

security-jwt-quickstart ディレクトリー にあるソリューションリポジトリーには、このクイックスタートガイドで使用したすべてのバージョンと、CDI API を使用して挿入された JsonWebToken およびそのクレームを持つサブリソースを示すいくつかの追加のエンドポイントが含まれます。クイックスタートソリューションを確認し、security-jwt-quickstart ディレクトリーを調べて、SmallRye JWT エクステンションの詳細を確認することをお勧めします。

リファレンスガイド

サポートされている注入スコープ

org.eclipse.microprofile.jwt.JsonWebToken が注入される場合、@ApplicationScoped@Singleton、および @RequestScoped の外部 Bean 注入スコープがすべてサポートされます。また、現在のトークンが示されていることを確認するために、JsonWebToken@RequestScoped スコープが適用されます。

ただし、個々のトークンクレームが String などの単純な型として挿入される場合は、@RequestScoped を使用する必要があります。以下はその例です。

package org.acme.security.jwt;

import javax.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 注入要件 を参照してください。

サポートされている公開鍵形式

公開鍵には、優先順位に従って指定された次のいずれかの形式が適用さされます。

  • 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 エンコード

検証鍵の処理

非対称 RSA または楕円曲線 (EC) 鍵を使用してトークンの署名を検証する必要がある場合は、mp.jwt.verify.publickey.location プロパティーを使用してローカルまたはリモートの検証鍵を参照します。

mp.jwt.verify.publickey.algorithm を使用して検証アルゴリズムをカスタマイズします (デフォルトは RS256)。たとえば、EC 鍵を操作する場合は ES256 に設定します。

対称秘密鍵を使用してトークン署名を検証する必要がある場合は、JSON Web 鍵 (JWK) または JSON Web 鍵セット (JWK セット) 形式のいずれかを使用して、この秘密鍵を表す必要があります。以下はその例です。

{
 "keys": [
   {
     "kty":"oct",
     "kid":"secretKey",
     "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
   }
 ]
}

この秘密鍵 JWK も、smallrye.jwt.verify.key.location で参照する必要があります。smallrye.jwt.verify.algorithmHS256/HS384/HS512 に設定する必要があります。

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 を指定できます。

import javax.crypto.SecretKey;
import javax.ws.rs.GET;
import javax.ws.rs.core.NewCookie;
import javax.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 {
  @Inject JWTParser parser;
  private String secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMko";

  @GET
  @Produces("text/plain")
  public Response getUserName(@CookieParam("jwt") String jwtCookie) {
    Response response = null;
    if (jwtCookie == null) {
        // Create a JWT token signed using the 'HS256' algorithm
        String newJwtCookie = Jwt.upn("Alice").signWithSecret(secret);
        // or create a JWT token encrypted 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();
    }
  }
}

SmallRye JWT を直接追加する方法 セクションで、quarkus-smallrye-jwt によって提供される HTTP サポートなしで JWTParser を使用する方法についても参照してください。

トークン復号化

アプリケーションが、暗号化されたクレームまたは暗号化された内部署名クレームを含むトークンを受け入れる必要がある場合、復号化キーを指す smallrye.jwt.decrypt.key.location を設定するだけでかまいません。

これが設定されている唯一のキープロパティーである場合、受信トークンには暗号化されたクレームのみが含まれていると予想されます。mp.jwt.verify.publickey または mp.jwt.verify.publickey.location 検証プロパティーも設定されている場合、受信トークンには暗号化された内部署名トークンが含まれている必要があります。

SmallRye JWT を使用した JWT トークンの生成 を参照し、暗号化または内部署名されてから暗号化されたトークンをすぐに生成する方法を確認してください。

カスタムファクトリー

io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipalFactory は、デフォルトで JWT トークンを解析および検証し、それらを JsonWebToken プリンシパルに変換するために使用されます。設定セクションにリストされている MP JWT および smallrye-jwt プロパティーを使用して、JWT トークンを検証およびカスタマイズします。

たとえば、ファイアウォールによってすでに検証されているトークンの再検証を回避するために独自のファクトリーを提供する必要がある場合は、META-INF/services/io.smallrye.jwt.auth.principal.JWTCallerPrincipalFactory リソースを提供すして ServiceLoader メカニズムを使用するか、単純に次のような Alternative CDI Bean 実装を持つことができます。

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.annotation.Priority;
import javax.enterprise.context.ApplicationScoped;
import javax.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());
        }
    }
}

トークンの伝播

下流サービスへのベアラアクセストークンの伝播については、トークン伝播 の項を参照してください。

テスト

Wiremock

HTTPS または HTTP ベースの JsonWebKey (JWK) セットを指すように mp.jwt.verify.publickey.location を設定する場合は、OpenID Connect Bearer トークン統合テストWiremock セクションで説明したのと同じアプローチを使用できますが、その場合は代わりに MPJWT 設定プロパティーを使用するように 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

Keycloak

Keycloak を使用しており、さらに HTTPS または HTTP ベースの JsonWebKey (JWK) セットを指すように mp.jwt.verify.publickey.location を設定する場合は、OpenID Connect Bearer トークン統合テスト の Keycloak セクションで説明したのと同じアプローチを使用できますが、その場合は代わりに MPJWT 設定プロパティーを使用するように 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

ローカル公開鍵

OpenID Connect Bearer トークン統合テストLocal Public Key セクションで説明したのと同じアプローチを使用できますが、その場合は代わりに MPJWT 設定プロパティーを使用するように 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

TestSecurity アノテーション

以下の依存関係を追加します。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-security-jwt</artifactId>
    <scope>test</scope>
</dependency>
build.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 アノテーションはオプションであり、追加のトークンクレームを設定するために使用できます。

ログでエラーを確認する方法

トークンの検証または復号化エラーの詳細を確認するには、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

プロアクティブ認証

パブリックエンドポイントメソッドが呼び出されたときにトークンの検証をスキップする場合は、プロアクティブ認証 を無効にしてください。

トークンの検証が実行されていない場合、パブリックメソッドで挿入された JsonWebToken にアクセスできないことに注意してください。

SmallRye JWT を直接追加する方法

HTTP をサポートしない Quarkus エクステンション (たとえば Quarkus GRPC) を使用する、または quarkus-smallrye-jwtVert.x HTTP によって提供されるものと競合する独自のエクステンション固有の HTTP サポート (たとえば Quarkus Amazon Lambda) を提供する場合で、JWTParser を使用した JsonWebToken の解析と検証 を行う場合、quarkus-smallrye-jwt の代わりに smallrye-jwt を直接使用してください。

次の依存関係を追加します。

pom.xml
<dependency>
    <groupId>io.smallrye</groupId>
    <artifactId>smallrye-jwt</artifactId>
</dependency>
build.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

設定リファレンス

Quarkus の設定

ビルド時に固定される設定プロパティ - その他の設定プロパティはランタイムでオーバーライド可能です。

Configuration property

デフォルト

The MP-JWT configuration object

Environment variable: QUARKUS_SMALLRYE_JWT_ENABLED

boolean

true

The name of the java.security.Provider that supports SHA256withRSA signatures

Environment variable: QUARKUS_SMALLRYE_JWT_RSA_SIG_PROVIDER

string

SunRsaSign

Enable this property if fetching the remote keys can be a time-consuming operation. Do not enable it if you use the local keys.

Environment variable: QUARKUS_SMALLRYE_JWT_BLOCKING_AUTHENTICATION

boolean

false

MicroProfile JWT の設定

プロパティ名e デフォルト 説明

mp.jwt.verify.publickey

none

mp.jwt.verify.publickey 設定プロパティーを使用すると、公開鍵テキスト自体を文字列として提供できます。公開鍵は、 サポートされている公開鍵形式 セクションで定義された順序で、提供された文字列から解析されます。

mp.jwt.verify.publickey.location

none

Config プロパティーを使用すると、公開鍵の外部または内部の場所を指定できます。値は相対パスまたは URL の場合があります。値が HTTPS ベースの JWK セットを指している場合、ネイティブモードで機能するには、quarkus.ssl.native プロパティーも true に設定する必要があります。詳細については ネイティブ実行可能ファイルで SSL を使用する場合 を参照してください。

mp.jwt.verify.publickey.algorithm

RS256

署名アルゴリズム。 ES256 に設定して、楕円曲線署名アルゴリズムをサポートします。

mp.jwt.decrypt.key.location

none

Config プロパティーを使用すると、秘密復号化鍵の外部または内部の場所を指定できます。

mp.jwt.verify.issuer

none

Config プロパティーは、サーバーが有効として受け入れる JWT の iss (発行者) クレームの値を指定します。

mp.jwt.verify.audiences

none

トークン aud クレームに含まれる可能性のあるオーディエンスのコンマ区切りリスト。

mp.jwt.token.header

Authorization

Cookie などの別のヘッダーを使用してトークンを渡す場合は、このプロパティーを設定します。

mp.jwt.token.cookie

none

トークンを含む Cookie の名前。このプロパティーは、mp.jwt.token.headerCookie に設定されている場合にのみ有効になります。

追加の SmallRye JWT 設定

SmallRye JWTには、トークン処理をカスタマイズするために使用できる、より多くのプロパティーが用意されています。

プロパティ名e デフォルト 説明

smallrye.jwt.verify.key.location

NONE

公開鍵と秘密鍵の両方を指すことができる検証鍵の場所。秘密鍵は JWK 形式でのみ使用できます。このプロパティーが設定されている場合、'mp.jwt.verify.publickey.location' は無視されることに注意してください。

smallrye.jwt.verify.algorithm

署名アルゴリズム。このプロパティーは、HS256 などの必要な対称アルゴリズムを設定するためにのみ使用する必要があります。ES256 などの非対称アルゴリズムの設定に使用することは推奨されません。代わりに 'mp.jwt.verify.publickey.algorithm' を使用してください。

smallrye.jwt.verify.key-format

ANY

このプロパティーを PEM_KEYPEM_CERTIFICATEJWKJWK_BASE64URL などの特定のキー鍵形式に設定して、検証鍵のロード方法を最適化します。

smallrye.jwt.verify.relax-key-validation

false

検証鍵の検証を緩和します。このプロパティーを true に設定すると、2048 ビット未満の長さの公開 RSA 鍵が許可されます。

smallrye.jwt.verify.certificate-thumbprint

false

このプロパティーが有効になっている場合、署名されたトークンには 'x5t' または 'x5t#S256' X509Certificatex5t 拇印ヘッダーが含まれている必要があります。この場合、検証鍵は JWK または PEM 証明書鍵形式でのみ使用できます。JWK 鍵には、'x5c' (Base64 でエンコードされた X509Certificate) プロパティーが設定されている必要があります。

smallrye.jwt.token.header

Authorization

Cookie などの別のヘッダーを使用してトークンを渡す場合は、このプロパティーを設定します。このプロパティーは推奨されていません。'mp.jwt.token.header' を使用してください。

smallrye.jwt.token.cookie

none

トークンを含む Cookie の名前。このプロパティーは、smallrye.jwt.token.headerCookie に設定されている場合にのみ有効になります。このプロパティーは推奨されていません。'mp.jwt.token.cookie` を使用してください。

smallrye.jwt.always-check-authorization

false

smallrye.jwt.token.headerCookie に設定されていても、名前が smallrye.jwt.token.cookie の Cookie が存在しない場合、Authorization ヘッダーがチェックされるようにこのプロパティーを true に設定します。

smallrye.jwt.token.schemes

Bearer

DPoP などの単一または複数の代替スキームを含むコンマ区切りのリスト。

smallrye.jwt.token.kid

none

鍵の識別子。設定されている場合、検証 JWK 鍵とすべての JWT トークンには、一致する kid ヘッダーが必要です。

smallrye.jwt.time-to-live

none

JWT を使用するために発行できる最大秒数。事実上、JWT の有効期限と発行日との差はこの値を超えてはなりません。このプロパティーを正でない値に設定すると、トークンが有効な 'iat' (で発行された) クレームを持つための要件が緩和されます。

smallrye.jwt.require.named-principal

true

アプリケーションが名前を返す java.security.Principal に依存している場合、トークンには upn または preferred_username または sub クレームが設定されている必要があります。このプロパティーを設定すると、アプリケーションコードが null 以外の Principal 名を確実に処理するためにこれらのクレームのいずれも使用できない場合に、SmallRye JWT は例外を出力します。

smallrye.jwt.path.sub

none

件名を含むクレームへのパス。これはトップレベルの JSON オブジェクトから始まり、複数のセグメントを含めることができます。各セグメントは JSON オブジェクト名のみを表します (例: realms/subject)。このプロパティーは、トークンに sub クレームがないが、サブジェクトが別のクレームに設定されている場合に使用できます。namespace で修飾されたクレームには二重引用符を使用します。

smallrye.jwt.claims.sub

none

現在のトークンに使用可能な標準またはカスタムの sub クレームがない場合に、このプロパティーを使用して、デフォルトのサブクレーム値を設定できます。事実上、このプロパティーを使用して、upnpreferred_usernamesub`クレームが設定されていない場合に `java.security.Principal 名をカスタマイズできます。

smallrye.jwt.path.groups

none

グループを含むクレームへのパス。これはトップレベルの JSON オブジェクトから始まり、複数のセグメントを含めることができます。各セグメントは JSON オブジェクト名のみを表します (例: realm/groups)。このプロパティーは、トークンに 'groups' クレームがないが、グループが別のクレームに設定されている場合に使用できます。namespace で修飾されたクレームには二重引用符を使用します。

smallrye.jwt.groups-separator

' '

複数のグループ値を含む可能性のある文字列を分割するためのセパレーター。これは、smallrye.jwt.path.groups プロパティーが値が文字列のカスタムクレームを指している場合にのみ使用されます。標準の OAuth2 scope クレームにはスペースで区切られたシーケンスが含まれている可能性があるため、デフォルト値はスペース 1 個です。

smallrye.jwt.claims.groups

none

このプロパティーを使用して、現在のトークンに使用可能な標準またはカスタムのグループクレームがない場合に、デフォルトのグループクレーム値を設定できます。

smallrye.jwt.jwks.refresh-interval

60

JWK キャッシュの更新間隔 (分単位)。mp.jwt.verify.publickey.location が HTTP または HTTPS URL ベースの JWK セットを指し、正の max-age パラメーター値を持つ HTTP Cache-Control レスポンスヘッダーが JWK HTTPS エンドポイントから返されない限り無視されます。

smallrye.jwt.jwks.forced-refresh-interval

30

強制 JWK キャッシュ更新間隔 (分単位)。現在のトークンの kid ヘッダーと一致する kid プロパティーを持つ JWK キーがキャッシュにないためにトークンの検証が失敗した場合に発生する可能性がある強制更新の試行頻度を制限するために使用されます。mp.jwt.verify.publickey.location が HTTP または HTTPS URL ベースの JWK セットを指していない限り、無視されます。

smallrye.jwt.expiration.grace

60

有効期限の猶予 (秒単位)。デフォルトでは、現在の時刻がトークンの有効期限から 1 分以内であれば、期限切れのトークンは引き続き受け入れられます。

smallrye.jwt.verify.aud

none

トークン aud クレームに含まれる可能性のあるオーディエンスのコンマ区切りリスト。このプロパティーは推奨されません。mp.jwt.verify.audiences を使用してください。

smallrye.jwt.required.claims

none

トークンに含める必要のあるクレームのコンマ区切りリスト。

smallrye.jwt.decrypt.key.location

none

Config プロパティーを使用すると、秘密復号化鍵の外部または内部の場所を指定できます。このプロパティーは推奨されません。'mp.jwt.decrypt.key.location' を使用してください。

smallrye.jwt.decrypt.algorithm

RSA_OAEP

復号化アルゴリズム。

smallrye.jwt.decrypt.key

none

文字列として提供される復号化キー。

smallrye.jwt.token.decryption.kid

none

復号化キー識別子。設定されている場合、復号化 JWK キーとすべての JWT トークンには、一致する kid ヘッダーが必要です。

smallrye.jwt.client.tls.certificate.path

none

キーを HTTPS 経由でフェッチする必要がある場合は、TLS の信頼できる証明書へのパスを設定する必要があります。

smallrye.jwt.client.tls.trust-all

false

すべてのホスト名を信頼します。キーを HTTPS 経由でフェッチする必要があり、このプロパティーが true に設定されている場合、デフォルトですべてのホスト名が信頼されます。

smallrye.jwt.client.tls.hosts

none

信頼できるホスト名のセット。キーを HTTPS 経由でフェッチする必要があり、smallrye.jwt.client.tls.trust-allfalse に設定されている場合、このプロパティーを使用して信頼できるホスト名を設定できます。

smallrye.jwt.http.proxy.host

none

HTTP プロキシーホスト。

smallrye.jwt.http.proxy.port

80

HTTP プロキシーポート。

smallrye.jwt.keystore.type

JKS

mp.jwt.verify.publickey.location または mp.jwt.decrypt.key.locationKeyStore ファイルを指している場合、このプロパティーを使用してキーストアタイプをカスタマイズできます。設定されていない場合は、ファイル名がチェックされ、JKS がデフォルトで設定される前にキーストアタイプが決定されます。

smallrye.jwt.keystore.provider

mp.jwt.verify.publickey.location または mp.jwt.decrypt.key.locationKeyStore ファイルを指している場合、このプロパティーを使用して KeyStore プロバイダーをカスタマイズできます。

smallrye.jwt.keystore.password

キーストアのパスワード。mp.jwt.verify.publickey.location または mp.jwt.decrypt.key.location の場合、このプロパティーが設定されています。

smallrye.jwt.keystore.verify.key.alias

このプロパティーは、mp.jwt.verify.publickey.location`が`KeyStore`ファイルを指している場合に、`KeyStore から抽出される公開検証鍵を一致する証明書から識別するために設定する必要があります。

smallrye.jwt.keystore.decrypt.key.alias

このプロパティは、 mp.jwt.decrypt.key.locationKeyStore ファイルを指している場合、プライベートな復号鍵を特定するために設定する必要があります。

smallrye.jwt.keystore.decrypt.key.password

このプロパティは、 mp.jwt.decrypt.key.locationKeyStore ファイルを指しているときに、 KeyStore にある秘密鍵のパスワードが smallrye.jwt.keystore.password と異なる場合に設定されることがあります。