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

Web アプリケーションを保護するための OpenID Connect 認可コードフローメカニズム

Quarkus OIDC エクステンションが提供する業界標準の OpenID Connect (OIDC) 認可コードフローメカニズムを使用して、Web アプリケーションを保護することができます。

OIDC 認可コードフローメカニズムの概要

Quarkus OpenID Connect (OIDC) エクステンションは、Keycloak などの OIDC 準拠の認可サーバーでサポートされている OIDC 認可コードフローメカニズムを使用して、アプリケーション HTTP エンドポイントを保護できます。

認可コードフローメカニズムは、Web アプリケーションのユーザーを Keycloak などの OIDC プロバイダーにリダイレクトしてログインさせることで、ユーザーを認証します。 認証後、OIDC プロバイダーは、認証が成功したことを確認する認可コードを使用してユーザーをアプリケーションにリダイレクトします。 次に、アプリケーションは、このコードを OIDC プロバイダーと交換して、ID トークン (認証されたユーザーを表す)、アクセストークン、およびリフレッシュトークンを取得し、ユーザーのアプリケーションへのアクセスを認可します。

次の図は、Quarkus における認可コードフローメカニズムの概要を示しています。

Authorization Code Flow
Figure 1. Quarkus における認可コードフローメカニズム
  1. Quarkus ユーザーは、Quarkus web-app アプリケーションへのアクセスをリクエストします。

  2. Quarkus web-app は、ユーザーを認可エンドポイント、つまり認証用の OIDC プロバイダーにリダイレクトします。

  3. OIDC プロバイダーは、ユーザーをログインと認証のプロンプトにリダイレクトします。

  4. プロンプトで、ユーザーは自分のユーザークレデンシャルを入力します。

  5. OIDC プロバイダーは、入力されたユーザークレデンシャルを認証し、認証に成功すると認可コードを発行し、そのコードをクエリーパラメーターとして含めて Quarkus web-app にユーザーをリダイレクトします。

  6. Quarkus web-app は、この認可コードを OIDC プロバイダーと交換し、ID、アクセス、およびリフレッシュの各トークンを取得します。

認可コードフローが完了し、Quarkus web-app は発行されたトークンを使用してユーザーに関する情報にアクセスし、そのユーザーに関連するロールベースの認可を付与します。 発行されるトークンは以下のとおりです。

  • IDトークン: Quarkus web-app アプリケーションは、ID トークンのユーザー情報を使用して、認証されたユーザーが安全にログインできるようにし、Web アプリケーションにロールベースのアクセスを提供します。

  • アクセストークン: Quarkus web-app は、アクセストークンを使用して UserInfo API にアクセスし、認証されたユーザーに関する追加情報を取得したり、別のエンドポイントに伝播したりすることがあります。

  • リフレッシュトークン: (オプション) ID およびアクセストークンの有効期限が切れた場合、Quarkus web-app はリフレッシュトークンを使用して新しい ID およびアクセストークンを取得できます。

OIDC設定プロパティ のリファレンスガイドもご参照ください。

OIDC 認可コードフローメカニズムを使用して Web アプリケーションを保護する方法については、OIDC 認可コードフローを使用した Web アプリケーションの保護 を参照してください。

OIDC ベアラートークン認証を使用してサービスアプリケーションを保護したい場合は、OIDC ベアラートークン認証 を参照してください。

マルチテナントへの対応方法については、OpenID Connect (OIDC) マルチテナンシーの使用 ガイドをお読みください。

認可コードフローメカニズムの利用について

OIDC プロバイダーエンドポイントへのアクセス設定

OIDC web-app アプリケーションは、OIDC プロバイダーの認可、トークン、JsonWebKey (JWK) セット、そして場合によっては UserInfo、イントロスペクション、セッション終了 (RP-initiated logout) エンドポイントの URL を必要とします。

規約により、quarkus.oidc.auth-server-url で設定することで /.well-known/openid-configuration のパスを追加することで検出されます。

また、ディスカバリーエンドポイントが利用できない場合や、ディスカバリーエンドポイントのラウンドトリップを減らしたい場合は、エンドポイントのディスカバリーを無効にし、相対パス値を設定することもできます。 例:

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.discovery-enabled=false
# Authorization endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/auth
quarkus.oidc.authorization-path=/protocol/openid-connect/auth
# Token endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/token
quarkus.oidc.token-path=/protocol/openid-connect/token
# JWK set endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/certs
quarkus.oidc.jwks-path=/protocol/openid-connect/certs
# UserInfo endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/userinfo
quarkus.oidc.user-info-path=/protocol/openid-connect/userinfo
# Token Introspection endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/token/introspect
quarkus.oidc.introspection-path=/protocol/openid-connect/token/introspect
# End-session endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/logout
quarkus.oidc.end-session-path=/protocol/openid-connect/logout

一部の OIDC プロバイダーは、メタデータディスカバリーをサポートしていますが、認可コードフローを完了したり、ユーザーログアウトなどのアプリケーション機能をサポートしたりするために必要なすべてのエンドポイント URL 値は返しません。 この制限を回避するには、次の例に示すように、不足しているエンドポイント URL 値をローカルで設定します。

# Metadata is auto-discovered but it does not return an end-session endpoint URL

quarkus.oidc.auth-server-url=http://localhost:8180/oidcprovider/account

# Configure the end-session URL locally.
# It can be an absolute or relative (to 'quarkus.oidc.auth-server-url') address
quarkus.oidc.end-session-path=logout

検出されたエンドポイント URL がローカルの Quarkus エンドポイントで機能せず、より具体的な値が必要な場合は、この同じ設定を使用して、エンドポイント URL をオーバーライドできます。 たとえば、グローバルエンドセッションエンドポイントとアプリケーション固有のエンドセッションエンドポイントの両方をサポートするプロバイダーは、http://localhost:8180/oidcprovider/account/global-logout のようなグローバルエンドセッション URL を返します。 この URL は、ユーザーが現在ログインしているすべてのアプリケーションからユーザーをログアウトします。 ただし、現在のアプリケーションで特定のアプリケーションのみからユーザーをログアウトさせる必要がある場合は、quarkus.oidc.end-session-path=logout パラメーターを設定することで、グローバルエンドセッション URL をオーバーライドできます。

OIDC プロバイダーのクライアント認証

OIDC プロバイダーは通常、OIDC エンドポイントとやり取りする際に、アプリケーションの識別と認証を行う必要があります。 Quarkus OIDC、特に quarkus.oidc.runtime.OidcProviderClient クラスは、認可コードを ID、アクセス、リフレッシュトークンと交換する必要がある場合、または ID やアクセストークンをリフレッシュまたはイントロスペクトする必要がある場合に、OIDC プロバイダーで認証します。

通常、クライアント ID とクライアントシークレットは、アプリケーション毎に OIDC プロバイダーに登録される際に定義されます。 すべての OIDC クライアント認証 オプションがサポートされています。 例:

client_secret_basic の例:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=mysecret

あるいは:

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.client-secret.value=mysecret

次の例は、クレデンシャルプロバイダー から取得したシークレットを示しています。

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app

# This is a key which will be used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc.credentials.client-secret.provider.key=mysecret-key
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc.credentials.client-secret.provider.name=oidc-credentials-provider
client_secret_post の例
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.client-secret.value=mysecret
quarkus.oidc.credentials.client-secret.method=post
client_secret_jwt の例。署名アルゴリズムは HS256:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
client_secret_jwt の例。この場合、シークレットは クレデンシャルプロバイダー から取得されます。
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app

# This is a key which will be used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc.credentials.jwt.secret-provider.key=mysecret-key
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc.credentials.jwt.secret-provider.name=oidc-credentials-provider

PEM 鍵ファイルを使用した private_key_jwt の例。署名アルゴリズムは RS256:

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key-file=privateKey.pem
keystoreファイルによる private_key_jwt の例。署名アルゴリズムはRS256:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key-store-file=keystore.jks
quarkus.oidc.credentials.jwt.key-store-password=mypassword
quarkus.oidc.credentials.jwt.key-password=mykeypassword

# Private key alias inside the keystore
quarkus.oidc.credentials.jwt.key-id=mykeyAlias

client_secret_jwt、または private_key_jwt 認証方法を使用することで、クライアントシークレットが OIDC プロバイダーに送信されないため、「中間者」攻撃によってシークレットが傍受されるリスクを回避できます。

JWT 認証の追加オプション

client_secret_jwtprivate_key_jwt、または Apple の post_jwt 認証方法が使用されている場合、JWT 署名アルゴリズム、キー識別子、audience、subject、および issuer をカスタマイズすることができます。 例:

# private_key_jwt client authentication

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key-file=privateKey.pem

# This is a token key identifier 'kid' header - set it if your OIDC provider requires it:
# Note if the key is represented in a JSON Web Key (JWK) format with a `kid` property, then
# using 'quarkus.oidc.credentials.jwt.token-key-id' is not necessary.
quarkus.oidc.credentials.jwt.token-key-id=mykey

# Use RS512 signature algorithm instead of the default RS256
quarkus.oidc.credentials.jwt.signature-algorithm=RS512

# The token endpoint URL is the default audience value, use the base address URL instead:
quarkus.oidc.credentials.jwt.audience=${quarkus.oidc-client.auth-server-url}

# custom subject instead of the client id:
quarkus.oidc.credentials.jwt.subject=custom-subject

# custom issuer instead of the client id:
quarkus.oidc.credentials.jwt.issuer=custom-issuer
Apple POST JWT

Apple OIDC プロバイダーは、private_key_jwt 認証方式で生成された JWT をシークレットとする client_secret_post 方式を使用していますが、Apple アカウント固有の issuer と subject のクレームを使用しています。

Quarkus Security では、quarkus-oidc は、非標準の client_secret_post_jwt 認証方法をサポートしています。この認証方法は、以下のように設定できます。

# Apple provider configuration sets a 'client_secret_post_jwt' authentication method
quarkus.oidc.provider=apple

quarkus.oidc.client-id=${apple.client-id}
quarkus.oidc.credentials.jwt.key-file=ecPrivateKey.pem
quarkus.oidc.credentials.jwt.token-key-id=${apple.key-id}
# Apple provider configuration sets ES256 signature algorithm

quarkus.oidc.credentials.jwt.subject=${apple.subject}
quarkus.oidc.credentials.jwt.issuer=${apple.issuer}
相互 TLS (mTLS)

OIDC プロバイダーによっては、相互 TLS 認証プロセスの一部としてクライアントの認証を要求する場合があります。

次の例は、mTLS をサポートするように quarkus-oidc を設定する方法を示しています。

quarkus.oidc.tls.verification=certificate-validation

# Keystore configuration
quarkus.oidc.tls.key-store-file=client-keystore.jks
quarkus.oidc.tls.key-store-password=${key-store-password}

# Add more keystore properties if needed:
#quarkus.oidc.tls.key-store-alias=keyAlias
#quarkus.oidc.tls.key-store-alias-password=keyAliasPassword

# Truststore configuration
quarkus.oidc.tls.trust-store-file=client-truststore.jks
quarkus.oidc.tls.trust-store-password=${trust-store-password}
# Add more truststore properties if needed:
#quarkus.oidc.tls.trust-store-alias=certAlias
POST クエリー

一部のプロバイダー ( Strava OAuth2 プロバイダーなど ) では、クライアントのクレデンシャルを HTTP POST クエリーパラメーターとして投稿する必要があります。

quarkus.oidc.provider=strava
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.client-secret.value=mysecret
quarkus.oidc.credentials.client-secret.method=query

イントロスペクションエンドポイント認証

一部の OIDC プロバイダーでは、Basic 認証と client_id および client_secret とは異なるクレデンシャルを使用して、イントロスペクションエンドポイントへの認証を行う必要があります。 OIDCプロバイダークライアント認証 セクションに記載されているように、以前にセキュリティー認証を設定して、client_secret_basic または client_secret_post クライアント認証メソッドをサポートしていた場合は、次のように追加の設定を適用することを推奨します。

トークンをイントロスペクションする必要があり、イントロスペクションエンドポイント固有の認証メカニズムが必要な場合は、quarkus-oidc を以下のように設定できます。

quarkus.oidc.introspection-credentials.name=introspection-user-name
quarkus.oidc.introspection-credentials.secret=introspection-user-secret

OIDC リクエストフィルター

1 つまたは複数の OidcRequestFilter 実装を登録することで、Quarkus から OIDC プロバイダーへの OIDC リクエストをフィルタリングすることが出来、リクエストヘッダーを更新または追加したり、リクエストをログに記録したりすることができます。

例:

package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcRequestContextProperties;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.vertx.mutiny.core.buffer.Buffer;
import io.vertx.mutiny.ext.web.client.HttpRequest;

@ApplicationScoped
@Unremovable
public class OidcTokenRequestCustomizer implements OidcRequestFilter {
    @Override
    public void filter(HttpRequest<Buffer> request, Buffer buffer, OidcRequestContextProperties contextProps) {
        OidcConfigurationMetadata metadata = contextProps.get(OidcConfigurationMetadata.class.getName()); (1)
        // Metadata URI is absolute, request URI value is relative
        if (metadata.getTokenUri().endsWith(request.uri())) { (2)
            request.putHeader("TokenGrantDigest", calculateDigest(buffer.toString()));
        }
    }
    private String calculateDigest(String bodyString) {
        // Apply the required digest algorithm to the body string
    }
}
1 サポートされているすべての OIDC エンドポイントアドレスが含まれる OidcConfigurationMetadata を取得します。
2 OidcConfigurationMetadata を使用して、OIDC トークンエンドポイントへのリクエストのみをフィルターします。

あるいは、OidcRequestFilter.Endpoint 列挙型を使用して、このフィルターをトークンエンドポイントリクエストにのみ適用することもできます。

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcRequestContextProperties;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.vertx.mutiny.core.buffer.Buffer;
import io.vertx.mutiny.ext.web.client.HttpRequest;

@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.DISCOVERY) (1)
public class OidcDiscoveryRequestCustomizer implements OidcRequestFilter {

    @Override
    public void filter(HttpRequest<Buffer> request, Buffer buffer, OidcRequestContextProperties contextProps) {
        request.putHeader("Discovery", "OK");
    }
}
1 このフィルターを、OIDC 検出エンドポイントのみを対象とするリクエストに制限します。

OIDC プロバイダーへのリダイレクトと OIDC プロバイダーからのリダイレクト

ユーザーが認証のために OIDC プロバイダーにリダイレクトされる場合、リダイレクト URL には redirect_uri クエリーパラメーターが含まれます。このパラメーターは、認証完了後のユーザーのリダイレクト先をプロバイダーに示します。 ここでのリダイレクト先は Quarkus アプリケーションになります。

Quarkus は、デフォルトでこのパラメーターを現在のアプリケーションリクエスト URL に設定します。 たとえば、ユーザーが http://localhost:8080/service/1 にある Quarkus サービスエンドポイントにアクセスしようとしている場合、redirect_uri パラメーターは http://localhost:8080/service/1 に設定されます。 同様に、リクエスト URL が http://localhost:8080/service/2 の場合、redirect_uri パラメーターは http://localhost:8080/service/2 に設定されます。

一部の OIDC プロバイダーでは、すべてのリダイレクト URL の特定のアプリケーションに対して、redirect_uri が同じ値 (例: http://localhost:8080/service/callback) を持つことが必要です。 このような場合は、quarkus.oidc.authentication.redirect-path プロパティーを設定する必要があります。 たとえば、quarkus.oidc.authentication.redirect-path=/service/callback の場合、Quarkus は redirect_uri パラメーターを http://localhost:8080/service/callback などの絶対 URL に設定します。これは、現在のリクエスト URL に関係なく同じになります。

quarkus.oidc.authentication.redirect-path が設定されているが、ユーザーが http://localhost:8080/service/callback などの一意のコールバック URL にリダイレクトされた後に元のリクエスト URL を復元する必要がある場合は、quarkus.oidc.authentication.restore-path-after-redirect プロパティーを true に設定します。 これにより、http://localhost:8080/service/1 などのリクエスト URL が復元されます。

認証リクエストのカスタマイズ

デフォルトでは、ユーザーが認証のために OIDC プロバイダーの認可エンドポイントにリダイレクトされる際に、response_type (code に設定)、scope (openid に設定)、client_idredirect_uri、および state プロパティーのみが HTTP クエリーパラメーターとして、OIDC プロバイダーの認可エンドポイントに渡されます。

quarkus.oidc.authentication.extra-params を使用して、さらにプロパティーを追加できます。 たとえば、一部の OIDC プロバイダーは、リダイレクト URI のフラグメントの一部として認可コードを返すことを選択する場合があり、これにより認証プロセスが中断されます。 次の例は、この問題を回避する方法を示しています。

quarkus.oidc.authentication.extra-params.response_mode=query

認証エラーレスポンスのカスタマイズ

ユーザーが OIDC 認可エンドポイントにリダイレクトされ、Quarkus アプリケーションを認証し、必要に応じて認可する場合、このリダイレクトリクエストは、リダイレクト URI に無効なスコープが含まれている場合などに失敗する可能性があります。 このような場合、プロバイダーは、想定される code パラメーターではなく、error および error_description パラメーターを使用してユーザーを Quarkus にリダイレクトします。

これは、たとえば、プロバイダーへのリダイレクトに無効なスコープまたはその他の無効なパラメーターが含まれている場合に発生する可能性があります。

このような場合、デフォルトで HTTP 401 エラーが返されます。 ただし、ユーザーにわかりやすいエラーメッセージを返すために、カスタムパブリックエラーエンドポイントを呼び出すようにリクエストできます。 これを行うには、quarkus.oidc.authentication.error-path プロパティーを以下のように設定します。

quarkus.oidc.authentication.error-path=/error

プロパティーがフォワードスラッシュ (/) 文字で始まり、パスが現在のエンドポイントのベース URI と相対的であるようにします。 たとえば、これが '/error' に設定され、現在のリクエスト URI が https://localhost:8080/callback?error=invalid_scope の場合、最終的に https://localhost:8080/error?error=invalid_scope へリダイレクトされます。

ユーザーがこのページにリダイレクトされて再認証されないようにするには、このエラーエンドポイントがパブリックリソースであるようにします。

認可データへのアクセス

認可に関する情報には、さまざまな方法でアクセスできます。

ID およびアクセストークンへのアクセス

OIDC コード認証メカニズムは、認可コードフロー中に 3 つのトークン IDトークン、アクセストークン、リフレッシュトークンを取得します。

ID トークンは常に JWT トークンであり、JWT クレームによるユーザー認証を表します。 これを使って発行元の OIDC エンドポイントやユーザー名、その他 クレーム と呼ばれる情報を取得することができます。 JsonWebTokenIdToken という修飾子をつけることで、ID トークンのクレームにアクセスすることができます。

import jakarta.inject.Inject;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.IdToken;
import io.quarkus.security.Authenticated;

@Path("/web-app")
@Authenticated
public class ProtectedResource {

    @Inject
    @IdToken
    JsonWebToken idToken;

    @GET
    public String getUserName() {
        return idToken.getName();
    }
}

OIDC web-app アプリケーションは通常、アクセストークンを使用して、現在ログインしているユーザーに代わって他のエンドポイントにアクセスします。 raw アクセストークンには次のようにアクセスできます。

import jakarta.inject.Inject;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.security.Authenticated;

@Path("/web-app")
@Authenticated
public class ProtectedResource {

    @Inject
    JsonWebToken accessToken;

    // or
    // @Inject
    // AccessTokenCredential accessTokenCredential;

    @GET
    public String getReservationOnBehalfOfUser() {
        String rawAccessToken = accessToken.getRawToken();
        //or
        //String rawAccessToken = accessTokenCredential.getToken();

        // Use the raw access token to access a remote endpoint.
        // For example, use RestClient to set this token as a `Bearer` scheme value of the HTTP `Authorization` header:
        // `Authorization: Bearer rawAccessToken`.
        return getReservationfromRemoteEndpoint(rawAccesstoken);
    }
}

AccessTokenCredential は、Quarkus web-app アプリケーションに発行されたアクセストークンが不透明 (バイナリー) で、JsonWebToken に解析できない場合、または内部コンテンツがアプリケーションに必要な場合に使用されます。

JsonWebTokenAccessTokenCredential の注入は、@RequestScoped@ApplicationScoped の両方のコンテキストでサポートされています。

Quarkus OIDC は、セッション管理 プロセスの一環として、リフレッシュトークンを使用し、現在の ID とアクセストークンを更新します。

ユーザー情報

ID トークンが現在認証されているユーザーに関する十分な情報を提供しない場合は、UserInfo エンドポイントから詳細情報を取得できます。 quarkus.oidc.authentication.user-info-required=true プロパティーを設定して、OIDC UserInfo エンドポイントから UserInfo JSON オブジェクトをリクエストします。

認可コードの付与応答で返されたアクセストークンを使用して、OIDC プロバイダーの UserInfo エンドポイントにリクエストが送信され、io.quarkus.oidc.UserInfo (単純な jakarta.json.JsonObject ラッパー) オブジェクトが作成されます。 io.quarkus.oidc.UserInfo は、SecurityIdentity userinfo 属性として注入またはアクセスできます。

OIDC 設定情報へのアクセス

現在のテナントの検出された OpenID Connect 設定メタデータ は、io.quarkus.oidc.OidcConfigurationMetadata で表され、SecurityIdentity configuration-metadata 属性として注入またはアクセスできます。

エンドポイントがパブリックの場合、デフォルトのテナントの OidcConfigurationMetadata が注入されます。

トークンクレームと SecurityIdentity ロールのマッピング

検証済みトークンから SecurityIdentity ロールにロールをマッピングする方法は、ベアラートークン の方法と同じです。 唯一の違いは、 ID トークン がデフォルトでロールのソースとして使用されることです。

Keycloak を使用する場合は、ID トークンに groups クレームが含まれるように microprofile-jwt クライアントスコープを設定します。 詳細は、Keycloak サーバー管理ガイド のリンクを参照してください。

ただし、OIDC プロバイダーによっては、ロールがアクセストークンまたはユーザー情報に保存される場合があります。

アクセストークンにロールが含まれており、このアクセストークンがダウンストリームエンドポイントに伝播されることを意図していない場合は、quarkus.oidc.roles.source=accesstoken を設定します。

UserInfo がロールのソースである場合は、quarkus.oidc.roles.source=userinfo を設定し、必要に応じて quarkus.oidc.roles.role-claim-path を設定します。

さらに、カスタム SecurityIdentityAugmentor を使用してロールを追加することもできます。 詳細は、SecurityIdentity カスタマイズ を参照してください。 また、HTTP Security ポリシー を使用して、トークン要求から作成された SecurityIdentity ロールをデプロイメント固有のロールにマップすることもできます。

トークンと認証データの有効性の確認

認証プロセスの中核となるのは、トラストチェーンと情報の有効性を確認することです。 これは、トークンが信頼できることを確認することによって行われます。

トークン検証およびイントロスペクション

OIDC 認可コードフロートークンの検証プロセスは、ベアラートークン認証トークンの検証とイントロスペクションのロジックに従います。 詳細は、「Quarkus OpenID Connect (OIDC) ベアラートークン認証」ガイドの トークンの検証とイントロスペクション のセクションを参照してください。

Quarkus の web-app アプリケーションでは、アクセストークンは現在の Quarkus の web-app エンドポイントへのアクセスには使用されず、このアクセストークンを必要とするサービスに伝播されることが意図されているため、デフォルトでは IdToken のみが検証されます。 アクセストークンに現在の Quarkus エンドポイント (quarkus.oidc.roles.source=accesstoken) にアクセスするために必要なロールが含まれていることが予想される場合は、それも検証されます。

トークンのイントロスペクションと UserInfo のキャッシュ

コードフローアクセストークンは、ロールのソースであると期待されない限り、イントロスペクトされません。 ただし、アクセストークンは UserInfo を取得するために使用されます。 トークンイントロスペクション または UserInfo、あるいはその両方が必要な場合は、コードフローアクセストークンを使用したリモート呼び出しが 1 回または 2 回発生します。

デフォルトのトークンキャッシュの使用またはカスタムキャッシュ実装の登録の詳細は、Token introspection と UserInfo キャッシュ を参照してください。

JSON Web トークンのクレーム検証

iss (発行者) クレームを含むクレーム検証の詳細は、 JSON Web Token クレーム検証 セクションを参照してください。 これは、ID トークンに適用され、web-app アプリケーションがアクセストークンの検証をリクエストした場合は、JWT 形式のアクセストークンにも適用されます。

Proof Key for Code Exchange (PKCE) を使用したセキュリティー強化

Proof Key for Code Exchange (PKCE) は、認可コードの傍受のリスクを最小限に抑えます。

PKCE は、ブラウザーで実行される SPA スクリプトなどのパブリック OIDC クライアントにとって最も重要ですが、Quarkus OIDC web-app アプリケーションに追加の保護を提供することもできます。 PKCE を使用すると、Quarkus OIDC web-app アプリケーションは、クライアントシークレットを安全に保存し、それを使用してトークンのコードを交換できる機密 OIDC クライアントとして機能します。

次の例に示すように、quarkus.oidc.authentication.pkce-required プロパティーと、stateクッキーの PKCE コード検証を暗号化するために必要な 32 文字のシークレットを使用して、OIDC web-app エンドポイントの PKCE を有効にすることができます。

quarkus.oidc.authentication.pkce-required=true
quarkus.oidc.authentication.state-secret=eUk1p7UB3nFiXZGUXi0uph1Y9p34YhBU

すでに 32 文字のクライアントシークレットがある場合は、別のシークレットキーを使用する場合を除き、quarkus.oidc.authentication.pkce-secret プロパティーを設定する必要はありません。 このシークレットは、設定されていない場合、およびクライアントシークレットの長さが 16 文字未満でクライアントシークレットへのフォールバックが不可能な場合に、自動生成されます。

シークレットキーは、ランダムに生成された PKCE code_verifier を暗号化するために必要です。一方、ユーザーは code_challenge クエリーパラメーターを使用して OIDC プロバイダーにリダイレクトされ、認証されます。 code_verifier は、ユーザーが Quarkus にリダイレクトされるときに復号化され、code、クライアントシークレット、およびその他のパラメーターとともにトークンエンドポイントに送信され、コード交換が完了します。 code_verifierSHA256 ダイジェストが、認証のリクエスト時に提供された code_challenge と一致しない場合、プロバイダーはコード交換に失敗します。

認証のライフタイムの処理と制御

認証のもう 1 つの重要な要件は、ユーザーがリクエストのたびに認証をリクエストすることなく、セッションの基となるデータが最新であることを保証することです。 また、ログアウトイベントが明示的にリクエストされる状況もあります。 以下のポイントを参考に、Quarkus アプリケーションのセキュリティーを確保するための適切なバランスを見つけて下さい。

クッキー

OIDC アダプターはクッキーを使用して、セッション、コードフロー、ログアウト後の状態を保持します。 この状態は、認証データの寿命を制御する重要な要素です。

quarkus.oidc.authentication.cookie-path プロパティーを使用すると、保護されたリソースに重複または異なるルートでアクセスしたときに、同じクッキーが表示されるようになります。 例:

  • /index.html/web-app/service

  • /web-app/service1/web-app/service2

  • /web-app1/service/web-app2/service

デフォルトでは、quarkus.oidc.authentication.cookie-path/ に設定されていますが、必要に応じてこれを /web-app などのより具体的なパスに変更できます。

クッキーパスを動的に設定するには、quarkus.oidc.authentication.cookie-path-header プロパティーを設定します。 quarkus.oidc.authentication.cookie-path-header プロパティーを設定します。 たとえば、X-Forwarded-Prefix HTTP ヘッダーの値を使用してクッキーパスを動的に設定するには、プロパティーを quarkus.oidc.authentication.cookie-path-header=X-Forwarded-Prefix に設定します。

quarkus.oidc.authentication.cookie-path-header が設定されているが、現在のリクエストで設定された HTTP ヘッダーが利用できない場合は、quarkus.oidc.authentication.cookie-path がチェックされます。

アプリケーションが複数のドメインにまたがってデプロイされている場合は、セッションクッキー がすべての保護された Quarkus サービスに表示されるように、quarkus.oidc.authentication.cookie-domain プロパティーを設定します。 たとえば、次の 2 つのドメインに Quarkus サービスをデプロイしている場合は、quarkus.oidc.authentication.cookie-domain プロパティーを company.net に設定する必要があります。

  • https://whatever.wherever.company.net/

  • https://another.address.company.net/

セッションクッキーとデフォルトの TokenStateManager

OIDC CodeAuthenticationMechanism は、デフォルトの io.quarkus.oidc.TokenStateManager インターフェイス実装を使用して、認可コードまたはリフレッシュ付与応答で返された ID、アクセストークン、およびリフレッシュトークンを暗号化されたセッションクッキーに保存します。

これにより、Quarkus OIDC エンドポイントは完全にステートレスになり、最高のスケーラビリティー結果を達成するには、このストラテジーに従うことが推奨されます。

トークンを保存するための代替方法については、このガイドの Database TokenStateManager および セッションクッキーとカスタムTokenStateManager のセクションを参照してください。 たとえば、トークンの状態をサーバー上に保存することを好んだり、それにふさわしい理由があったりする場合は、トークンをデータベースまたはその他のサーバー側ストレージに保存します。

デフォルトの TokenStateManager を設定すると、アクセストークンをセッションクッキーに保存せずに、ID トークンとリフレッシュトークンのみ、または単一の ID トークンのみを保持できます。

アクセストークンは、エンドポイントが次のアクションを実行する必要がある場合にのみ必要です。

  • UserInfo の取得

  • このアクセストークンを使用したダウンストリームサービスへのアクセス

  • デフォルトでチェックされるアクセストークンに関連付けられたロールの使用

このような場合は、quarkus.oidc.token-state-manager.strategy プロパティーを使用して、トークン状態ストラテジーを次のように設定します。

以下の場合 プロパティーを以下に設定する

ID とリフレッシュトークンのみを保存する場合

quarkus.oidc.token-state-manager.strategy=id-refresh-tokens

ID トークンのみを保存する場合

quarkus.oidc.token-state-manager.strategy=id-token

選択したセッションクッキーストラテジーがトークンを組み合わせ、4 KB を超える大きなセッションクッキー値が生成される場合、一部のブラウザーではそのようなクッキーサイズを処理できない可能性があります。 これは、ID、アクセストークン、およびリフレッシュトークンが JWT トークンで、選択されたストラテジーが keep-all-tokens の場合、またはストラテジーが id-refresh-token の場合にID トークンとリフレッシュトークンで発生する可能性があります。 この問題を回避するには、quarkus.oidc.token-state-manager.split-tokens=true を設定して、トークンごとに一意のセッショントークンを作成します。 別の解決策として、トークンをデータベースに保存することが挙げられます。 詳細は、Database TokenStateManager を参照してください。

デフォルトの TokenStateManager は、トークンをセッションクッキーに保存する前に暗号化します。 次の例は、トークンを分割して暗号化するように設定する方法を示しています。

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.token-state-manager.split-tokens=true
quarkus.oidc.token-state-manager.encryption-secret=eUk1p7UB3nFiXZGUXi0uph1Y9p34YhBU

トークンの暗号化シークレットは 32 文字以上である必要があります。 このキーが設定されていない場合は、quarkus.oidc.credentials.secret または quarkus.oidc.credentials.jwt.secret のいずれかがハッシュ化されて暗号化キーが作成されます。

Quarkus が次のいずれかの認証方法を使用して OIDC プロバイダーに対して認証する場合は、quarkus.oidc.token-state-manager.encryption-secret プロパティーを設定してください。

  • mTLS

  • private_key_jwt では、秘密の RSA または EC キーを使用して JWT トークンに署名します。

これ以外の場合は、ランダムなキーが生成されますが、Quarkus アプリケーションがクラウドで実行され、複数の Pod がリクエストを管理している場合、問題が発生する可能性があります。

quarkus.oidc.token-state-manager.encryption-required=false を設定することで、セッションクッキーでのトークン暗号化を無効にすることができます。

セッションクッキーとカスタムTokenStateManager

トークンをセッションクッキーに関連付ける方法をカスタマイズしたい場合は、カスタム io.quarkus.oidc.TokenStateManager 実装を @ApplicationScoped CDI Bean として登録します。

たとえば、トークンをキャッシュクラスターに保持し、キーのみをセッションクッキーに保存することを推奨します。 トークンを複数のマイクロサービスノードで利用できるようにする必要がある場合、このアプローチではいくつかの課題が生じる可能性があることに注意してください。

簡単な例を挙げてみます。

package io.quarkus.oidc.test;

import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import jakarta.inject.Inject;

import io.quarkus.oidc.AuthorizationCodeTokens;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.TokenStateManager;
import io.quarkus.oidc.runtime.DefaultTokenStateManager;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
@Alternative
@Priority(1)
public class CustomTokenStateManager implements TokenStateManager {

    @Inject
    DefaultTokenStateManager tokenStateManager;

    @Override
    public Uni<String> createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig,
            AuthorizationCodeTokens sessionContent, TokenStateManager.CreateTokenStateRequestContext requestContext) {
        return tokenStateManager.createTokenState(routingContext, oidcConfig, sessionContent, requestContext)
                .map(t -> (t + "|custom"));
    }

    @Override
    public Uni<AuthorizationCodeTokens> getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig,
            String tokenState, TokenStateManager.GetTokensRequestContext requestContext) {
        if (!tokenState.endsWith("|custom")) {
            throw new IllegalStateException();
        }
        String defaultState = tokenState.substring(0, tokenState.length() - 7);
        return tokenStateManager.getTokens(routingContext, oidcConfig, defaultState, requestContext);
    }

    @Override
    public Uni<Void> deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState,
            TokenStateManager.DeleteTokensRequestContext requestContext) {
        if (!tokenState.endsWith("|custom")) {
            throw new IllegalStateException();
        }
        String defaultState = tokenState.substring(0, tokenState.length() - 7);
        return tokenStateManager.deleteTokens(routingContext, oidcConfig, defaultState, requestContext);
    }
}

暗号化されたセッションクッキーにトークンを保存するデフォルトの TokenStateManager の詳細は、セッションクッキーとデフォルトの TokenStateManager を参照してください。

トークンをデータベースに保存するカスタム Quarkus TokenStateManager 実装の詳細は、Database TokenStateManager を参照してください。

Database TokenStateManager

ステートフルなトークンストレージストラテジーを採用したい場合は、Quarkus が提供するカスタムの TokenStateManager を使用して、アプリケーションがトークンを暗号化されたセッションクッキーに保存するのではなく、データベースに保存することができます。これは、セッションクッキーとデフォルトの TokenStateManager セクションに記載されているとおり、デフォルトで設定されています。

この機能を使用するには、以下のエクステンションをプロジェクトに追加します。

コマンドラインインタフェース
quarkus extension add oidc-db-token-state-manager
Maven
./mvnw quarkus:add-extension -Dextensions='oidc-db-token-state-manager'
Gradle
./gradlew addExtension --extensions='oidc-db-token-state-manager'

このエクステンションは、デフォルトの io.quarkus.oidc.TokenStateManager をデータベースをベースとするものに置き換えます。

OIDC Database Token State Manager は、認証が IO スレッドで行われる可能性が高いため、ブロックを回避するために内部で Reactive SQL クライアントを使用します。

データベースに応じて、Reactive SQL クライアント を 1 つだけ含めて設定します。 次の Reactive SQL クライアントがサポートされています。

  • Reactive Microsoft SQL クライアント

  • Reactive MySQL クライアント

  • Reactive PostgreSQL クライアント

  • Reactive Oracle クライアント

  • Reactive DB2 クライアント

アプリケーションがすでにいずれかの JDBC ドライバーエクステンションを備えた Hibernate ORM を使用している場合は、Reactive SQL クライアントを使用するように切り替える必要はありません。

たとえば、Hibernate ORM エクステンションと PostgreSQL JDBC ドライバーを併用するアプリケーションがすでにあり、データソースが次のように設定されているとします。

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test

ここで、OIDC Database Token State Manager を使用することにした場合は、次の依存関係を追加し、リアクティブドライバー URL を設定する必要があります。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc-db-token-state-manager</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-oidc-db-token-state-manager")
implementation("io.quarkus:quarkus-reactive-pg-client")
quarkus.datasource.reactive.url=postgresql://localhost:5432/quarkus_test

これで、トークンをデータベースに保存する準備が整いました。

デフォルトでは、トークンの保存に使用されるデータベーステーブルが作成されますが、quarkus.oidc.db-token-state-manager.create-database-table-if-not-exists 設定プロパティーを使用して、このオプションを無効にすることができます。 代わりに Hibernate ORM エクステンションでこのテーブルを作成する場合は、次のように Entity を含める必要があります。

package org.acme.manager;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Table(name = "oidc_db_token_state_manager") (1)
@Entity
public class OidcDbTokenStateManagerEntity {

    @Id
    String id;

    @Column(name = "id_token", length = 4000) (2)
    String idToken;

    @Column(name = "refresh_token", length = 4000)
    String refreshToken;

    @Column(name = "access_token", length = 4000)
    String accessToken;

    @Column(name = "expires_in")
    Long expiresIn;
}
1 Hibernate ORM エクステンションは、データベーススキーマが生成された場合にのみ、このテーブルを作成します。 詳細は、Hibernate ORM ガイドを参照してください。
2 トークンの長さに応じて列の長さを選択できます。

ログアウトと有効期限

認証情報が期限切れになる主な方法は 2 つあります。トークンの有効期限が切れて更新されなかった場合と、明示的なログアウト操作がトリガーされた場合です。

最初に、明示的なログアウト操作について説明します。

User-initiated logout

ユーザーは、quarkus.oidc.logout.path プロパティーで設定された Quarkus エンドポイントのログアウトパスにリクエストを送信することで、ログアウトをリクエストできます。 たとえば、エンドポイントアドレスが https://application.com/webapp で、quarkus.oidc.logout.path が "/logout" に設定されている場合、ログアウトリクエストは https://application.com/webapp/logout に送信される必要があります。

このログアウトリクエストは、 RP-initiated ログアウト を開始します。 ユーザはログアウトするために OIDC プロバイダーにリダイレクトされ、そこでログアウトが本当に意図されたものであるか確認されます。

ログアウトが完了し、quarkus.oidc.logout.post-logout-path プロパティーが設定されている場合、ユーザーはエンドポイントのログアウト後のページに戻されます。 たとえば、エンドポイントアドレスが https://application.com/webapp で、quarkus.oidc.logout.post-logout-path が "/signin" に設定されている場合、ユーザーは https://application.com/webapp/signin に戻されます。 この URI は、OIDC プロバイダーで有効な post_logout_redirect_uri として登録されている必要があることに注意してください。

quarkus.oidc.logout.post-logout-path が設定されている場合、q_post_logout クッキーが作成され、一致する state クエリーパラメーターがログアウトリダイレクト URI に追加され、ログアウトが完了すると OIDC プロバイダーはこの state を返します。 Quarkus の web-app アプリケーションでは、state クエリーパラメーターが q_post_logout クッキーの値と一致することを確認することを推奨します。これは、たとえば Jakarta REST フィルターで実行できます。

OpenID Connect Multi-Tenancy を使用する場合、クッキー名が異なることに注意してください。 たとえば、tenant_1 ID を持つテナントの場合は q_post_logout_tenant_1 という名前になります。

ログアウトフローを開始するように Quarkus アプリケーションを設定する方法の例を次に示します。

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=frontend
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app

quarkus.oidc.logout.path=/logout
# Logged-out users should be returned to the /welcome.html site which will offer an option to re-login:
quarkus.oidc.logout.post-logout-path=/welcome.html

# Only the authenticated users can initiate a logout:
quarkus.http.auth.permission.authenticated.paths=/logout
quarkus.http.auth.permission.authenticated.policy=authenticated

# All users can see the Welcome page:
quarkus.http.auth.permission.public.paths=/welcome.html
quarkus.http.auth.permission.public.policy=permit

また、quarkus.oidc.authentication.cookie-path をすべてのアプリケーションリソースに共通のパス値 (この例では /) に設定することも推奨します。 詳細は、Cookies セクションを参照してください。

一部の OIDC プロバイダーは、RP-initiated ログアウト 仕様をサポートしておらず、OpenID Connect の周知の(well-knownの) end_session_endpoint メタデータプロパティーを返しません。 ただし、このような OIDC プロバイダーの特定のログアウトメカニズムは、ログアウト URL クエリーパラメーターの命名方法のみが異なるため、Quarkus にとってはこれは問題ではありません。

RP-initiated ログアウト 仕様によれば、quarkus.oidc.logout.post-logout-path プロパティーは post_logout_redirect_uri クエリーパラメーターとして表されますが、この仕様をサポートしていないプロバイダーでは認識されません。

この問題を回避するには、quarkus.oidc.logout.post-logout-url-param を使用できます。 quarkus.oidc.logout.extra-params で追加されたログアウトクエリーパラメーターをさらにリクエストすることもできます。 たとえば、Auth0 でログアウトをサポートする方法は次のとおりです。

quarkus.oidc.auth-server-url=https://dev-xxx.us.auth0.com
quarkus.oidc.client-id=redacted
quarkus.oidc.credentials.secret=redacted
quarkus.oidc.application-type=web-app

quarkus.oidc.tenant-logout.logout.path=/logout
quarkus.oidc.tenant-logout.logout.post-logout-path=/welcome.html

# Auth0 does not return the `end_session_endpoint` metadata property. Instead, you must configure it:
quarkus.oidc.end-session-path=v2/logout
# Auth0 will not recognize the 'post_logout_redirect_uri' query parameter so ensure it is named as 'returnTo':
quarkus.oidc.logout.post-logout-uri-param=returnTo

# Set more properties if needed.
# For example, if 'client_id' is provided, then a valid logout URI should be set as the Auth0 Application property, without it - as Auth0 Tenant property:
quarkus.oidc.logout.extra-params.client_id=${quarkus.oidc.client-id}
バックチャネルログアウト

OIDC プロバイダーは、認証データを使用して、すべてのアプリケーションを強制的にログアウトさせることができます。 これはバックチャネルログアウトとして知られています。 この場合、OIDC は各アプリケーションから特定の URL を呼び出し、ログアウトをトリガーします。

OIDC プロバイダーは、バックチャネルログアウト を使用して、ユーザエージェントをバイパスして、そのユーザが現在ログインしているすべてのアプリケーションから現在のユーザをログアウトします。

バックチャネルログアウトをサポートするように Quarkus を設定するには、次のようにします。

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=frontend
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app

quarkus.oidc.logout.backchannel.path=/back-channel-logout

絶対的な back-channel logout URL は、現在のエンドポイント URL に quarkus.oidc.back-channel-logout.path を追加することによって計算されます (例: http://localhost:8080/back-channel-logout)。 この URL は、OIDC プロバイダーの管理コンソールで設定する必要があります。

OIDC プロバイダーが、現在のログアウトトークンに有効期限を設定していない場合、ログアウトトークンの検証が成功するようにトークンの有効期間プロパティーを設定する必要もあります。 たとえば、ログアウトトークンの iat (発行時刻) から 10 秒超過しないようにするには、quarkus.oidc.token.age=10S を設定します。

フロントチャネルログアウト

フロントチャネルログアウト のリンクを使用すると、ブラウザーなどのユーザーエージェントから現在のユーザーを直接ログアウトできます。 これは バックチャネルログアウト と似ていますが、ログアウト手順はブラウザーなどのユーザーエージェントによって実行され、OIDC プロバイダーがバックグラウンドで実行することはありません。 これは、ほとんど使用されないオプションです。

Quarkus でフロントチャネルのログアウトをサポートするには、以下のように設定します。

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=frontend
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app

quarkus.oidc.logout.frontchannel.path=/front-channel-logout

このパスは現在のリクエストのパスと比較され、これらのパスがマッチする 場合、ユーザはログアウトします。

ローカルログアウト

User-initiated logout は、ユーザーを OIDC プロバイダーからログアウトします。 これをシングルサインオンとして使用する場合は、必要とするものではない可能性があります。 たとえば、OIDC プロバイダーが Google の場合、Google とそのサービスからログアウトされます。 代わりに、ユーザーはその特定のアプリケーションからログアウトしたいだけかもしれません。 もう 1 つのユースケースとしては、OIDC プロバイダーにログアウトエンドポイントがない場合が考えられます。

OidcSession を使用することで、ローカルログアウトをサポートできます。つまり、次の例に示すように、ローカルセッションクッキーのみがクリアされます。

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import io.quarkus.oidc.OidcSession;

@Path("/service")
public class ServiceResource {

    @Inject
    OidcSession oidcSession;

    @GET
    @Path("logout")
    public String logout() {
        oidcSession.logout().await().indefinitely();
        return "You are logged out".
    }
OidcSession をローカルログアウトに使用

io.quarkus.oidc.OidcSession は現在の IdToken のラッパーで、Local logout の実行、現行セッションのテナント識別子の取得、セッションの有効期限の確認を行う上で役に立ちます。 今後、より便利なメソッドが追加される予定です。

セッション管理

デフォルトでは、ログアウトは OIDC プロバイダーによって発行された ID トークンの有効期限に基づいて行われます。 ID トークンの有効期限が切れると、Quarkus エンドポイントでの現在のユーザーセッションは無効になり、ユーザーは認証のために再度 OIDC プロバイダーにリダイレクトされます。 OIDC プロバイダーのセッションが引き続き有効な場合、ユーザーは再度クレデンシャルを入力することなく、自動的に再認証されます。

quarkus.oidc.token.refresh-expired プロパティーを有効にすると、現在のユーザーセッションを自動的に延長できます。 true に設定すると、現在の ID トークンの有効期限が切れたときに、リフレッシュトークンの付与を使用して、ID トークンだけでなくアクセストークンとリフレッシュトークンもリフレッシュされます。

keycloak.js などの OIDC プロバイダースクリプトが認可コードフローを管理している サービスアプリケーション用のシングルページアプリケーション がある場合、そのスクリプトは SPA 認証セッションの有効期間も制御します。

Quarkus OIDC web-app アプリケーションを使用する場合、Quarkus OIDC コード認証メカニズムがユーザーセッションの有効期間を管理します。

リフレッシュトークンを使用するには、セッションクッキーの有効期間を慎重に設定する必要があります。 セッションの有効期間は、ID トークンの有効期間よりも長く、リフレッシュトークンの有効期間と近いか等しい必要があります。

セッションの有効期間は、現在の ID トークンの有効期間の値と、quarkus.oidc.authentication.session-age-extension プロパティーおよび quarkus.oidc.token.lifespan-grace プロパティーの値を加算して計算します。

必要に応じて、quarkus.oidc.authentication.session-age-extension プロパティーのみを使用して、セッションの有効期間を大幅に延長します。 quarkus.oidc.token.lifespan-grace プロパティーは、小さなクロックスキューを考慮する場合にのみ使用します。

現在認証されているユーザーが、保護された Quarkus エンドポイントに戻り、セッションクッキーに関連付けられた ID トークンの有効期限が切れると、デフォルトでは、ユーザーは再認証のために OIDC 認可エンドポイントに自動的にリダイレクトされます。 ユーザーとこの OIDC プロバイダー間のセッションがまだアクティブな場合、OIDC プロバイダーはユーザーに再度チャレンジする可能性があります。これは、セッションが ID トークンよりも長く続くように設定されている場合に発生する可能性があります。

quarkus.oidc.token.refresh-expiredtrue に設定されている場合、期限切れの ID トークン (およびアクセストークン) は、初期認可コード付与応答で返されたリフレッシュトークンを使用して更新されます。 このリフレッシュトークンも、このプロセスの一環としてリサイクル (リフレッシュ) される可能性があります。 その結果、新しいセッションクッキーが作成され、セッションが延長されます。

ユーザーがあまりアクティブでない場合は、quarkus.oidc.authentication.session-age-extension プロパティーを使用して、期限切れの ID トークンを処理できます。 ID トークンの有効期限が切れると、クッキーの有効期間が経過するため、次のユーザーリクエスト時にセッションクッキーが Quarkus エンドポイントに返されない可能性があります。 Quarkus は、このリクエストが最初の認証リクエストであると想定します。 quarkus.oidc.authentication.session-age-extension を、ほとんどアクティブでないユーザーとセキュリティーポリシーに合わせて、適度に 長い値に設定します。

さらに一歩進んで、期限切れが近づいている ID トークンまたはアクセストークンを事前に更新することもできます。 quarkus.oidc.token.refresh-token-time-skew を、更新を予測する値に設定します。 現在のユーザーリクエスト中に、現在の ID トークンがこの quarkus.oidc.token.refresh-token-time-skew 内に期限切れになると計算された場合、トークンは更新され、新しいセッションクッキーが作成されます。 このプロパティーは、ID トークンの有効期間よりも短い値に設定する必要があります。この有効期間の値に近いほど、ID トークンの更新頻度が高くなります。

単純な JavaScript 関数を使用して Quarkus エンドポイントに定期的に ping を送信し、ユーザーアクティビティーをエミュレートすることで、このプロセスをさらに最適化できます。これにより、ユーザーが再認証される必要がある時間枠が最小限に抑えられます。

このユーザーセッションを無期限に延長することはできません。 有効期限が切れた ID トークンを持つ復帰ユーザーは、リフレッシュトークンの有効期限が切れると、OIDC プロバイダーエンドポイントで再認証する必要があります。

GitHub および OIDC 以外の OAuth2 プロバイダーとのインテグレーション

GitHub や LinkedIn のような有名なプロバイダーは OpenID Connect プロバイダーではなく、authorization code flow をサポートする OAuth2 プロバイダーです。 たとえば、GitHub OAuth2LinkedIn OAuth2 などです。 OIDC は OAuth2 の上に構築されていることを思い出してください。

OIDC プロバイダと OAuth2 プロバイダの主な違いは、OIDC プロバイダは OAuth2 プロバイダが返す標準認可コードフロー access および refresh トークンに加えて、ユーザ認証を表す ID Token を返すことです。

GitHub などの OAuth2 プロバイダは IdToken を返さないため、ユーザー認証は access トークンによって暗黙的かつ間接的に表現されます。 この access トークンは、現在のQuarkus web-app アプリケーションが認証されたユーザーに代わってデータにアクセスすることを認可する、認証されたユーザーを表します。

OIDC の場合は、認証の有効性の証明として ID トークンを検証しますが、OAuth2 の場合はアクセストークンを検証します。 これは、アクセストークンを必要とし、通常はユーザー情報を返すエンドポイントを後で呼び出すことによって実行されます。 これは、OIDC UserInfo と同様のアプローチで、ユーザーに代わって Quarkus OIDC が UserInfo を取得します。

たとえば、GitHub と連携する場合、Quarkus エンドポイントは access トークンを取得できます。これにより、Quarkus エンドポイントは現在のユーザーの GitHub プロファイルをリクエストできます。

このような OAuth2 サーバーとのインテグレーションをサポートするには、quarkus-oidc を少し異なる方法で設定して、IdToken: quarkus.oidc.authentication.id-token-required=false なしで認可コードフローの応答を許可する必要があります。

IdToken なしで認可コードフローをサポートするようにエクステンションを設定しても、quarkus-oidc の動作方法を標準化するために内部 IdToken が生成されます。 認証セッションをサポートし、リクエストごとにユーザーを GitHub などのプロバイダーにリダイレクトしないようにするには、IdToken を使用します。 この場合、セッションの有効期間は 5 分に設定されていますが、session management セクションに記載されているように、設定期間を延長することができます。

これにより、複数の OIDC プロバイダーをサポートするアプリケーションの取り扱いが簡単になります。

次の手順は、返されたアクセストークンが有用であり、現在の Quarkus エンドポイントに対して有効だと確認することです。 最初の方法は、quarkus.oidc.introspection-path を設定して OAuth2 プロバイダーのイントロスペクションエンドポイントを呼び出すことです (プロバイダーがそのようなエンドポイントを提供している場合)。 この場合、quarkus.oidc.roles.source=accesstoken を使用して、アクセストークンをロールのソースとして使用できます。 イントロスペクションエンドポイントが存在しない場合は、代わりに UserInfo (少なくともアクセストークンを検証するため) をプロバイダーにリクエストすることを試行できます。 これを行うには、quarkus.oidc.token.verify-access-token-with-user-info=true を指定します。 また、quarkus.oidc.user-info-path プロパティーを、ユーザー情報を取得する URL エンドポイント (またはアクセストークンによって保護されたエンドポイント) に設定する必要もあります。 GitHub の場合、イントロスペクションエンドポイントがないため、UserInfo をリクエストする必要があります。

UserInfo が必要な場合、すべてのリクエストに対してリモート呼び出しが行われます。 したがって、UserInfo データのキャッシュを推奨します。 詳細は、「OpenID Connect (OIDC) ベアラートークン認証」ガイドの トークンイントロスペクションと UserInfo キャッシュ セクションを参照してください。

あるいは、quarkus.oidc.cache-user-info-in-idtoken=true プロパティーを使用して、UserInfo が内部生成された IdToken に埋め込まれるようにリクエストすることを推奨します。 このアプローチの利点は、デフォルトでは、キャッシュされた UserInfo 状態がエンドポイントに保持されず、代わりにセッションクッキーに保存されることです。 UserInfo に機密データが含まれている場合は、このケースで IdToken を暗号化することも推奨します。 詳細は、Encrypt tokens with TokenStateManager を参照してください。

OAuth2 サーバーは、よく知られた設定エンドポイントをサポートしていない可能性があります。 この場合、検出を無効にして、認可、トークン、イントロスペクション、および UserInfo エンドポイントパスを手動で設定する必要があります。

Apple、Facebook、GitHub、Google、Microsoft、Spotify、Twitter などのよく知られた OIDC または OAuth2 プロバイダーの場合、Quarkus は quarkus.oidc.provider プロパティーを使用して、アプリケーションの設定を大幅に簡素化できます。 GitHub OAuth アプリケーションを作成 した後に、quarkus-oidc を GitHub と統合する方法は次のとおりです。 Quarkus エンドポイントを次のように設定します。

quarkus.oidc.provider=github
quarkus.oidc.client-id=github_app_clientid
quarkus.oidc.credentials.secret=github_app_clientsecret

# user:email scope is requested by default, use 'quarkus.oidc.authentication.scopes' to request different scopes such as `read:user`.
# See https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps for more information.

# Consider enabling UserInfo Cache
# quarkus.oidc.token-cache.max-size=1000
# quarkus.oidc.token-cache.time-to-live=5M
#
# Or having UserInfo cached inside IdToken itself
# quarkus.oidc.cache-user-info-in-idtoken=true

他のよく知られているプロバイダーの設定に関する詳細は、OpenID Connect プロバイダー を参照してください。

このようなエンドポイントに対して必要なのは、現在認証されているユーザーのプロファイルを GET http://localhost:8080/github/userinfo で返し、個々の UserInfo のプロパティーとしてアクセスすることです。

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

import io.quarkus.oidc.UserInfo;
import io.quarkus.security.Authenticated;

@Path("/github")
@Authenticated
public class TokenResource {

    @Inject
    UserInfo userInfo;

    @GET
    @Path("/userinfo")
    @Produces("application/json")
    public String getUserInfo() {
        return userInfo.getUserInfoString();
    }
}

OpenID Connect Multi-Tenancy を使用して複数のソーシャルプロバイダー (たとえば、IdToken を返す OIDC プロバイダーの Google と、IdToken を返さず UserInfo へのアクセスのみを許可する OAuth2 プロバイダーの GitHub) をサポートする場合、Google フローと GitHub フローの両方で、注入された SecurityIdentity のみを使用してエンドポイントを動作させることができます。 GitHub フローがアクティブな場合に、内部で生成された IdToken で作成されたプリンシパルが UserInfo ベースのプリンシパルに置き換えられる場合は、SecurityIdentity の簡単な拡張が必要になります。

package io.quarkus.it.keycloak;

import java.security.Principal;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.oidc.UserInfo;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomSecurityIdentityAugmentor implements SecurityIdentityAugmentor {

    @Override
    public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
        RoutingContext routingContext = identity.getAttribute(RoutingContext.class.getName());
        if (routingContext != null && routingContext.normalizedPath().endsWith("/github")) {
	        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
	        UserInfo userInfo = identity.getAttribute("userinfo");
	        builder.setPrincipal(new Principal() {

	            @Override
	            public String getName() {
	                return userInfo.getString("preferred_username");
	            }

	        });
	        identity = builder.build();
        }
        return Uni.createFrom().item(identity);
    }

}

これで、ユーザーが Google または GitHub を使用してアプリケーションにサインインすると、次のコードが機能するようになります。

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;

@Path("/service")
@Authenticated
public class TokenResource {

    @Inject
    SecurityIdentity identity;

    @GET
    @Path("/google")
    @Produces("application/json")
    public String getUserName() {
        return identity.getPrincipal().getName();
    }

    @GET
    @Path("/github")
    @Produces("application/json")
    public String getUserName() {
        return identity.getPrincipal().getUserName();
    }
}

おそらく、より簡単な代替案は、@IdToken JsonWebTokenUserInfo の両方を注入し、IdToken を返すプロバイダーを処理する際は JsonWebToken を使用して、IdToken を返さないプロバイダーの場合は UserInfo を使用することです。

GitHub OAuth アプリケーション設定に入力するコールバックパスが、GitHub 認証とアプリケーション認可が成功した後にユーザーをリダイレクトするエンドポイントパスと一致していることを確認する必要があります。 この場合は、http:localhost:8080/github/userinfo に設定する必要があります。

重要な認証イベントのリッスン

重要な OIDC 認証イベントを監視する @ApplicationScoped Bean を登録できます。 ユーザが初めてログインしたり、再認証したり、セッションをリフレッシュしたりすると、リスナーが更新されます。 将来的には、さらに多くのイベントが報告されるようになるかもしれません。 例:

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

import io.quarkus.oidc.IdTokenCredential;
import io.quarkus.oidc.SecurityEvent;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class SecurityEventListener {

    public void event(@Observes SecurityEvent event) {
        String tenantId = event.getSecurityIdentity().getAttribute("tenant-id");
        RoutingContext vertxContext = event.getSecurityIdentity().getAttribute(RoutingContext.class.getName());
        vertxContext.put("listener-message", String.format("event:%s,tenantId:%s", event.getEventType().name(), tenantId));
    }
}
「セキュリティのヒントとコツ」ガイドの セキュリティイベントの監視 セクションで説明されているように、他のセキュリティイベントを聞くことができます。

下流サービスへのトークンの伝播

認可コードフローから下流のサービスへのアクセストークンの伝播については、 トークンの伝播 のセクションを参照してください。

インテグレーションに関する考慮事項

OIDC によって保護されたアプリケーションは、シングルページアプリケーションから呼び出すことができる環境に統合されます。 これは、よく知られている OIDC プロバイダーと連携し、HTTP リバースプロキシーの背後で実行される必要があるほか、外部および内部アクセスなども必要とされます。

このセクションでは、これらの考慮事項について説明します。

シングルページアプリケーション

「OpenID Connect (OIDC) ベアラートークン認証」ガイドの シングルページアプリケーション セクションで提案されている方法でシングルページアプリケーション (SPA) を実装した場合、要件が満たされるか確認できます。

Quarkus Web アプリケーションで FetchXMLHttpRequest(XHR) などの SPA および JavaScript API を使用する場合は、Quarkus からのリダイレクト後にユーザーが認証される認可エンドポイントに対して、OIDC プロバイダーがクロスオリジンリソース共有 (CORS) をサポートしない可能性があることに注意してください。 Quarkus アプリケーションと OIDC プロバイダーが異なる HTTP ドメイン、ポート、またはその両方でホストされている場合、認証に失敗します。

このような場合は、quarkus.oidc.authentication.java-script-auto-redirect プロパティーを false に設定します。これにより、Quarkus は 499 ステータスコードと OIDC 値を含む WWW-Authenticate ヘッダーを返すように指示されます。

quarkus.oidc.authentication.java-script-auto-redirect プロパティーが false に設定されている場合に 499 ステータスコードが返されるようにするには、ブラウザースクリプトで現在のリクエストを JavaScript リクエストとして識別するためのヘッダーを設定する必要があります。

スクリプトエンジンがエンジン固有のリクエストヘッダーを設定する場合は、カスタム quarkus.oidc.JavaScriptRequestChecker Bean を登録できます。これにより、現在のリクエストが JavaScript リクエストであるかどうかが Quarkus に通知されます。たとえば、JavaScript エンジンが HX-Request: true などのヘッダーを設定する場合は、次のようにチェックできます。

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.oidc.JavaScriptRequestChecker;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomJavaScriptRequestChecker implements JavaScriptRequestChecker {

    @Override
    public boolean isJavaScriptRequest(RoutingContext context) {
        return "true".equals(context.request().getHeader("HX-Request"));
    }
}

ステータスコードが 499 の場合は、最後にリクエストされたページを再度読み込みます。

それ以外の場合は、ブラウザースクリプトを更新し、X-Requested-With ヘッダーに JavaScript 値を設定して、499 ステータスコードの場合は最後にリクエストされたページを再度読み込みする必要があります。

例:

Future<void> callQuarkusService() async {
    Map<String, String> headers = Map.fromEntries([MapEntry("X-Requested-With", "JavaScript")]);

    await http
        .get("https://localhost:443/serviceCall")
        .then((response) {
            if (response.statusCode == 499) {
                window.location.assign("https://localhost.com:443/serviceCall");
            }
         });
  }

クロスオリジンリソース共有

別のドメインで実行されているシングルページのアプリケーションからこのアプリケーションを利用する場合は、クロスオリジンリソース共有 (CORS) を設定する必要があります。 詳細は、「クロスオリジンリソース共有」ガイドの CORSフィルター のセクションを参照してください。

クラウドプロバイダーサービスの呼び出し

Google Cloud

Google Developer Consoles の BigQuery などの Google Cloud Services に対して OIDC 認可コードフロー権限を有効化している現在認証済みのユーザーに代わり、Quarkus OIDC web-app アプリケーションが、それらのサービスにアクセスできるようにすることが可能です。

Quarkiverse Google クラウドサービス を使用して、これを行うことができます。 追加する必要があるのは、 以下の例に示すように、 最新タグ サービス依存関係のみです。

pom.xml
<dependency>
    <groupId>io.quarkiverse.googlecloudservices</groupId>
    <artifactId>quarkus-google-cloud-bigquery</artifactId>
    <version>${quarkiverse.googlecloudservices.version}</version>
</dependency>
build.gradle
implementation("io.quarkiverse.googlecloudservices:quarkus-google-cloud-bigquery:${quarkiverse.googlecloudservices.version}")

次に、Google OIDC プロパティーを設定します。

quarkus.oidc.provider=google
quarkus.oidc.client-id={GOOGLE_CLIENT_ID}
quarkus.oidc.credentials.secret={GOOGLE_CLIENT_SECRET}
quarkus.oidc.token.issuer=https://accounts.google.com

Quarkus アプリケーションのリバースプロキシーの背後での実行

Quarkus アプリケーションがリバースプロキシー、ゲートウェイ、またはファイアウォールの背後で実行されている場合に、HTTP Host ヘッダーが内部 IP アドレスにリセットされたり、HTTPS 接続が終端されたりするなどして、OIDC 認証メカニズムが影響を受けることがあります。 たとえば、認可コードフローの redirect_uri パラメーターが、予期される外部ホストではなく内部ホストに設定されている場合があります。

このような場合、プロキシーによって転送された元のヘッダーを認識するように Quarkus を設定する必要があります。 詳細は、リバースプロキシーの背後での実行 Vert.x のドキュメントセクションを参照してください。

たとえば、Quarkus エンドポイントが Kubernetes Ingress の背後にあるクラスターで実行されている場合、計算された redirect_uri パラメーターが内部エンドポイントアドレスを指している可能性があるため、OIDC プロバイダーからこのエンドポイントへのリダイレクトが機能しない可能性があります。 この問題は、Kubernetes Ingress によって外部エンドポイントアドレスを表すように X-ORIGINAL-HOST が設定されている次の設定を使用することで解決できます。

quarkus.http.proxy.proxy-address-forwarding=true
quarkus.http.proxy.allow-forwarded=false
quarkus.http.proxy.enable-forwarded-host=true
quarkus.http.proxy.forwarded-host-header=X-ORIGINAL-HOST

quarkus.oidc.authentication.force-redirect-https-scheme プロパティーは、Quarkus アプリケーションが SSL 終了リバースプロキシーの背後で実行されている場合にも使用できます。

OIDC プロバイダーへの外部および内部アクセス

OIDC プロバイダーの外部からアクセス可能な認可、ログアウト、およびその他のエンドポイントは、自動検出された URL や内部 URL quarkus.oidc.auth-server-url とは異なる HTTP(S) URL を持つ場合があります。 このような場合、エンドポイントは発行者の検証の失敗を報告し、外部からアクセス可能な OIDC プロバイダーエンドポイントへのリダイレクトが失敗する可能性があります。

Keycloak を使用する場合は、KEYCLOAK_FRONTEND_URL システムプロパティーを外部からアクセス可能なベース URL に設定して起動します。 他の OIDC プロバイダーと連携する場合は、プロバイダーのドキュメントを確認してください。

OIDC SAML アイデンティティーブローカー

アイデンティティープロバイダーが OpenID Connect を実装しておらず、従来の XML ベースの SAML2.0 SSO プロトコルのみを実装している場合、quarkus-oidc を OIDC アダプターとして使用する場合と同じように、Quarkus を SAML 2.0 アダプターとして使用することはできません。

ただし、Keycloak、Okta、Auth0、Microsoft ADFS などの多くの OIDC プロバイダーは、OIDC から SAML 2.0 へのブリッジを提供しています。 OIDC プロバイダーで SAML 2.0 プロバイダーへのアイデンティティーブローカー接続を作成し、quarkus-oidc を使用してこの SAML 2.0 プロバイダーに対してユーザーを認証し、OIDC プロバイダーが OIDC と SAML 2.0 の通信を調整することができます。 Quarkus エンドポイントに関しては、同じ Quarkus セキュリティー、OIDC API、@AuthenticatedSecurityIdentity などのアノテーションなどを引き続き使用できます。

たとえば、Okta が SAML 2.0 プロバイダーで、Keycloak が OIDC プロバイダーだとします。 ここでは、KeycloakOkta SAML 2.0 プロバイダーと仲介するように設定する方法を説明する一般的なシーケンスを示します。

まず、Okta Dashboard/Applications に新しい SAML2 インテグレーションを作成します。

Okta Create SAML Integration

たとえば、OktaSaml と名前を付けます。

Okta SAML General Settings

次に、Keycloak SAML ブローカーエンドポイントを指すように設定します。 この時点で、Keycloak レルムの名前 (例: quarkus) を知っておく必要があります。Keycloak SAML ブローカーのエイリアスが saml であると仮定して、エンドポイントアドレスを http:localhost:8081/realms/quarkus/broker/saml/endpoint と入力します。 サービスプロバイダー (SP) エンティティー ID を http:localhost:8081/realms/quarkus と入力します。ここで、http://localhost:8081 は Keycloak ベースアドレスで、saml はブローカーエイリアスです。

Okta SAML Configuration

次に、この SAML インテグレーションを保存し、その Metadata URL をメモします。

Okta SAML Metadata

続いて、SAML プロバイダーを Keycloak に追加します。

まず、通常どおりに、新しいレルムを作成するか、既存のレルムを Keycloak にインポートします。 この場合、レルム名は quarkus にする必要があります。

次に、quarkus レルムのプロパティーで、Identity Providers に移動し、新しい SAML プロバイダーを追加します。

Keycloak Add SAML Provider

エイリアスは saml に設定され、Redirect URIhttp:localhost:8081/realms/quarkus/broker/saml/endpoint で、Service provider entity IDhttp:localhost:8081/realms/quarkus である点に注意してください。これらは、前の手順で Okta SAML Integration を作成するときに入力した値と同じです。

最後に、前の手順の最後に書き留めた Okta SAML Integration Metadata URL を指すように Service entity descriptor を設定します。

次に、必要に応じて、 Authentication/browser/Identity Provider Redirector config に移動し、Alias プロパティーと Default Identity Provider プロパティーの両方を saml に設定して、この Keycloak SAML プロバイダーをデフォルトプロバイダーとして登録できます。 デフォルトのプロバイダーとして設定しない場合は、認証時に Keycloak は次の 2 つのオプションを提供します。

  • SAML プロバイダーによる認証

  • 名前とパスワードを使用した Keycloak への直接認証

ここで、Quarkus OIDC web-app アプリケーションを、Keycloak quarkus レルム quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus を指すように設定します。 次に、Keycloak OIDC および Okta SAML 2.0 プロバイダーによって提供される OIDC から SAML へのブリッジを使用して、Quarkus ユーザーを Okta SAML 2.0 プロバイダーに認証する準備が整います。

Keycloak の場合と同様に、他の OIDC プロバイダーを設定して SAML ブリッジを提供することもできます。

テスト

別の OIDC のようなサーバーへの認証に関しては、テストが困難になることがよくあります。 Quarkus は、モックから OIDC プロバイダーのローカル実行まで、さまざまなオプションを提供します。

テストプロジェクトに以下の依存関係を追加することから始めます。

pom.xml
<dependency>
    <groupId>net.sourceforge.htmlunit</groupId>
    <artifactId>htmlunit</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>*</artifactId>
       </exclusion>
    </exclusions>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("net.sourceforge.htmlunit:htmlunit")
testImplementation("io.quarkus:quarkus-junit5")

Wiremock

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

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-oidc-server</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-test-oidc-server")

REST テストエンドポイントを準備し、application.properties を設定します。 以下に例を示します。

# keycloak.url is set by OidcWiremockTestResource
quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.client-id=quarkus-web-app
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app

最後にテストコードを書きます。例:

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.oidc.server.OidcWiremockTestResource;

@QuarkusTest
@QuarkusTestResource(OidcWiremockTestResource.class)
public class CodeFlowAuthorizationTest {

    @Test
    public void testCodeFlow() throws Exception {
        try (final WebClient webClient = createWebClient()) {
            // the test REST endpoint listens on '/code-flow'
            HtmlPage page = webClient.getPage("http://localhost:8081/code-flow");

            HtmlForm form = page.getFormByName("form");
            // user 'alice' has the 'user' role
            form.getInputByName("username").type("alice");
            form.getInputByName("password").type("alice");

            page = form.getInputByValue("login").click();

            assertEquals("alice", page.getBody().asText());
        }
    }

    private WebClient createWebClient() {
        WebClient webClient = new WebClient();
        webClient.setCssErrorHandler(new SilentCssErrorHandler());
        return webClient;
    }
}

OidcWiremockTestResourcealiceadmin ユーザーを認識します。 ユーザー alice にはデフォルトで user ロールしかありませんが、quarkus.test.oidc.token.user-roles システムプロパティーでカスタマイズできます。 ユーザー admin にはデフォルトで useradmin ロールがありますが、quarkus.test.oidc.token.user-roles システムプロパティーでカスタマイズできます。

さらに、OidcWiremockTestResource はトークンの発行者と対象ユーザーを https://service.example.com に設定します。これは、quarkus.test.oidc.token.issuer および quarkus.test.oidc.token.audience システムプロパティーを使用してカスタマイズできます。

OidcWiremockTestResource は、すべての OIDC プロバイダーをエミュレートするために使用できます。

Dev Services for Keycloak

Keycloak に対する結合テストには、Dev Services for Keycloak を使用することを推奨します。 Dev Services for Keycloak は、テストコンテナーを起動して初期化します。これにより、quarkus レルム、quarkus-app クライアント (secret シークレット) が作成され、alice (admin および user ロール) および bob (user ロール) ユーザーが追加されます。これらのプロパティーは、すべてカスタマイズできます。

まず、application.properties を準備します。 Dev Services for Keycloak は、実行中のテストコンテナーを指す quarkus.oidc.auth-server-url のほか、quarkus.oidc.client-id=quarkus-app および quarkus.oidc.credentials.secret=secret を登録するため、完全に空の application.properties ファイルから開始できます。

ただし、必要なすべての quarkus-oidc プロパティーがすでに設定されている場合は、quarkus.oidc.auth-server-urlDev Services for Keycloakprod プロファイルに関連付けるだけで、コンテナーを起動できます。 以下に例を示します。

%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus

テストを実行する前に、カスタムレルムファイルを Keycloak にインポートする必要がある場合は、次のように Dev Services for Keycloak を設定できます。

%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.keycloak.devservices.realm-path=quarkus-realm.json

最後に、Wiremock セクションの説明と同じ方法で、テストコードを作成します。 唯一の違いは、@QuarkusTestResource が不要になったことです。

@QuarkusTest
public class CodeFlowAuthorizationTest {
}

KeecycloakTestResourceLifecycleManager の使用

Dev Services for Keycloak を使用しない正当な理由がある場合にのみ、テストには KeycloakTestResourceLifecycleManager を使用してください。 Keycloak に対する結合テストが必要な場合は、Dev Services for Keycloak を使用してテストすることを推奨します。

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

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-keycloak-server</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-test-keycloak-server")

これは、Keycloak コンテナーを起動する io.quarkus.test.common.QuarkusTestResourceLifecycleManager の実装である io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager を提供します。

次に、Maven Surefire プラグインを次のように設定します (ネイティブイメージでテストする場合は、Maven Failsafe プラグインを同様に設定します)。

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <systemPropertyVariables>
            <!-- or, alternatively, configure 'keycloak.version' -->
            <keycloak.docker.image>${keycloak.docker.image}</keycloak.docker.image>
            <!--
              Disable HTTPS if required:
              <keycloak.use.https>false</keycloak.use.https>
            -->
        </systemPropertyVariables>
    </configuration>
</plugin>

次に、設定を行い、Wiremock セクションに説明されているのと同じ方法でテストコードを記述します。 唯一の違いは QuarkusTestResource の名前です。

import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;

@QuarkusTest
@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
public class CodeFlowAuthorizationTest {
}

KeycloakTestResourceLifecycleManageraliceadmin ユーザーを登録します。 ユーザー alice にはデフォルトで user ロールしかありませんが、keycloak.token.user-roles システムプロパティーでカスタマイズできます。 ユーザー admin にはデフォルトで useradmin ロールがありますが、keycloak.token.admin-roles システムプロパティーでカスタマイズできます。

デフォルトでは、KeycloakTestResourceLifecycleManager が HTTPS を使用して Keycloak インスタンスを初期化します。これは、keycloak.use.https=false を指定することで無効にすることができます。 デフォルトのレルム名は quarkus で、クライアント ID は quarkus-web-app です。必要に応じて、keycloak.realmkeycloak.web-app.client システムプロパティーを設定して値をカスタマイズしてください。

TestSecurity アノテーション

@TestSecurity および @OidcSecurity アノテーションを使用して、次の注入のいずれか、または 4 つすべてに依存する web-app アプリケーションエンドポイントコードをテストできます。

  • ID JsonWebToken

  • Access JsonWebToken

  • UserInfo

  • OidcConfigurationMetadata

詳細は、注入された JsonWebToken での TestingSecurityの使用 を参照してください。

ログでのエラー確認

トークン検証エラーの詳細を確認するには、io.quarkus.oidc.runtime.OidcProviderTRACE レベルのロギングを有効にする必要があります。

quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".level=TRACE
quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".min-level=TRACE

OidcProvider クライアントの初期化エラーの詳細を確認するには、io.quarkus.oidc.runtime.OidcRecorderTRACE レベルのロギングを有効にしてください。

quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".level=TRACE
quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".min-level=TRACE

アプリケーションのグローバルログレベルを変更するには、quarkus dev コンソールから j と入力します。

関連コンテンツ