OpenID Connect (OIDC) and OAuth2 client and filters
You can use Quarkus extensions for OpenID Connect and OAuth 2.0 access token management, focusing on acquiring, refreshing, and propagating tokens.
This includes the following:
-
Using
quarkus-oidc-client
,quarkus-oidc-client-reactive-filter
andquarkus-oidc-client-filter
extensions to acquire and refresh access tokens from OpenID Connect and OAuth 2.0 compliant Authorization Servers such as Keycloak. -
Using
quarkus-oidc-token-propagation-reactive
andquarkus-oidc-token-propagation
extensions to propagate the currentBearer
orAuthorization Code Flow
access tokens.
これらのエクステンションで管理されているアクセストークンをHTTP Authorization ベアラートークンとして使用して、リモートサービスにアクセスすることができます。
OidcClient
以下の依存関係を追加します。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client</artifactId>
</dependency>
The quarkus-oidc-client
extension provides a reactive io.quarkus.oidc.client.OidcClient
, which can be used to acquire and refresh tokens using SmallRye Mutiny Uni
and Vert.x WebClient
.
OidcClient
is initialized at build time with the IDP token endpoint URL, which can be auto-discovered or manually configured. OidcClient
uses this endpoint to acquire access tokens by using token grants such as client_credentials
or password
and refresh the tokens by using a refresh_token
grant.
Token endpoint configuration
デフォルトでは、トークンのエンドポイントアドレスは、設定された quarkus.oidc-client.auth-server-url
に /.well-known/openid-configuration
パスを追加することによって検出されます。
例えば、この Keycloak の URL があるとします。
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus
OidcClient
は、トークンのエンドポイント URL が http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/tokens
であることを検出します。
Alternatively, if the discovery endpoint is unavailable or you want to save on the discovery endpoint round-trip, you can disable the discovery and configure the token endpoint address with a relative path value. For example:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus
quarkus.oidc-client.discovery-enabled=false
# Token endpoint: http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/tokens
quarkus.oidc-client.token-path=/protocol/openid-connect/tokens
ディスカバリーなしでトークンエンドポイント URL を設定する、よりコンパクトな方法は、 quarkus.oidc-client.token-path
を絶対 URL に設定することです。
quarkus.oidc-client.token-path=http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/tokens
Setting quarkus.oidc-client.auth-server-url
and quarkus.oidc-client.discovery-enabled
is not required in this case.
Supported token grants
The main token grants that OidcClient
can use to acquire the tokens are the client_credentials
(default) and password
grants.
Client credentials grant
OidcClient
が client_credentials
グラントを使用するように設定する方法は以下の通りです。
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
The client_credentials
grant allows setting extra parameters for the token request by using quarkus.oidc-client.grant-options.client.<param-name>=<value>
. Here is how to set the intended token recipient by using the audience
parameter:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
# 'client' is a shortcut for `client_credentials`
quarkus.oidc-client.grant.type=client
quarkus.oidc-client.grant-options.client.audience=https://example.com/api
Password grant
OidcClient
が password
グラントを使用するように設定する方法は以下の通りです。
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=password
quarkus.oidc-client.grant-options.password.username=alice
quarkus.oidc-client.grant-options.password.password=alice
It can be further customized by using a quarkus.oidc-client.grant-options.password
configuration prefix, similar to how the client credentials grant can be customized.
Other grants
OidcClient
can also help acquire the tokens by using grants that require some extra input parameters that cannot be captured in the configuration. These grants are refresh_token
(with the external refresh token), authorization_code
, and two grants which can be used to exchange the current access token, namely, urn:ietf:params:oauth:grant-type:token-exchange
and urn:ietf:params:oauth:grant-type:jwt-bearer
.
If you need to acquire an access token and have posted an existing refresh token to the current Quarkus endpoint, you must use the refresh_token
grant. This grant employs an out-of-band refresh token to obtain a new token set.
In this case, configure OidcClient
as follows:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=refresh
Then you can use the OidcClient.refreshTokens
method with a provided refresh token to get the access token.
Using the urn:ietf:params:oauth:grant-type:token-exchange
or urn:ietf:params:oauth:grant-type:jwt-bearer
grants might be required if you are building a complex microservices application and want to avoid the same Bearer
token be propagated to and used by more than one service. See Token Propagation in MicroProfile RestClient Reactive filter and Token Propagation in MicroProfile RestClient filter for more details.
Using OidcClient
to support the authorization code
grant might be required if, for some reason, you cannot use the Quarkus OIDC extension to support Authorization Code Flow. If there is a very good reason for you to implement Authorization Code Flow, then you can configure OidcClient
as follows:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=code
Then, you can use the OidcClient.accessTokens
method to accept a Map of extra properties and pass the current code
and redirect_uri
parameters to exchange the authorization code for the tokens.
OidcClient
also supports the urn:openid:params:grant-type:ciba
grant:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=ciba
Then, you can use the OidcClient.accessTokens
method to accept a Map of extra properties and pass the auth_req_id
parameter to exchange the token authorization code.
OidcClient を直接使用する
以下のように OidcClient
を直接使用することができます。
import jakarta.inject.PostConstruct;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.Tokens;
@Path("/service")
public class OidcClientResource {
@Inject
OidcClient client;
volatile Tokens currentTokens;
@PostConstruct
public void init() {
currentTokens = client.getTokens().await().indefinitely();
}
@GET
public String getResponse() {
Tokens tokens = currentTokens;
if (tokens.isAccessTokenExpired()) {
// Add @Blocking method annotation if this code is used with Reactive RestClient
tokens = client.refreshTokens(tokens.getRefreshToken()).await().indefinitely();
currentTokens = tokens;
}
// Use tokens.getAccessToken() to configure MP RestClient Authorization header/etc
}
}
Inject tokens
You can inject Tokens
that use OidcClient
internally. Tokens
can be used to acquire the access tokens and refresh them if necessary:
import jakarta.inject.PostConstruct;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import io.quarkus.oidc.client.Tokens;
@Path("/service")
public class OidcClientResource {
@Inject Tokens tokens;
@GET
public String getResponse() {
// Get the access token, which might have been refreshed.
String accessToken = tokens.getAccessToken();
// Use the access token to configure MP RestClient Authorization header/etc
}
}
OidcClientsの使用
io.quarkus.oidc.client.OidcClients
は OidcClient
のコンテナーで、デフォルトの OidcClient
と、このように設定できる名前付きクライアントが含まれています。
quarkus.oidc-client.client-enabled=false
quarkus.oidc-client.jwt-secret.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.jwt-secret.client-id=quarkus-app
quarkus.oidc-client.jwt-secret.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
In this case, the default client is disabled with a client-enabled=false
property. The jwt-secret
client can be accessed like this:
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClients;
@Path("/clients")
public class OidcClientResource {
@Inject
OidcClients clients;
@GET
public String getResponse() {
OidcClient client = clients.getClient("jwt-secret");
//Use this client to get the token
}
}
If you also use OIDC multitenancy, and each OIDC tenant has its own associated
|
If you need, you can also create a new OidcClient
programmatically like this:
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClients;
import io.quarkus.oidc.client.OidcClientConfig;
import io.smallrye.mutiny.Uni;
@Path("/clients")
public class OidcClientResource {
@Inject
OidcClients clients;
@GET
public String getResponse() {
OidcClientConfig cfg = new OidcClientConfig();
cfg.setId("myclient");
cfg.setAuthServerUrl("http://localhost:8081/auth/realms/quarkus/");
cfg.setClientId("quarkus");
cfg.getCredentials().setSecret("secret");
Uni<OidcClient> client = clients.newClient(cfg);
//Use this client to get the token
}
}
Inject named OidcClient and tokens
In case of multiple configured OidcClient
objects, you can specify the OidcClient
injection target by the extra qualifier @NamedOidcClient
instead of working with OidcClients
:
package io.quarkus.oidc.client;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/clients")
public class OidcClientResource {
@Inject
@NamedOidcClient("jwt-secret")
OidcClient client;
@GET
public String getResponse() {
//Use the client to get the token
}
}
同じ修飾子を使用して、Tokens
注入に使用する OidcClient
を指定することができます。
@Provider
@Priority(Priorities.AUTHENTICATION)
@RequestScoped
public class OidcClientRequestCustomFilter implements ClientRequestFilter {
@Inject
@NamedOidcClient("jwt-secret")
Tokens tokens;
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + tokens.getAccessToken());
}
}
RestClient の Reactive ClientFilter で OidcClient を使用する
以下の Maven 依存関係を追加します。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client-reactive-filter</artifactId>
</dependency>
また、 io.quarkus:quarkus-oidc-client
も持ってくることに注意しましょう。
quarkus-oidc-client-reactive-filter
エクステンションは io.quarkus.oidc.client.filter.OidcClientRequestReactiveFilter
を提供します。
It works similarly to the way OidcClientRequestFilter
does (see Use OidcClient in MicroProfile RestClient client filter) - it uses OidcClient
to acquire the access token, refresh it if needed, and set it as an HTTP Authorization
Bearer
scheme value. The difference is that it works with Reactive RestClient and implements a non-blocking client filter that does not block the current IO thread when acquiring or refreshing the tokens.
IOスレッドのブロックを避けるために、 OidcClientRequestReactiveFilter
は実行されるまで最初のトークン取得を遅らせます。
You can selectively register OidcClientRequestReactiveFilter
by using either io.quarkus.oidc.client.reactive.filter.OidcClientFilter
or org.eclipse.microprofile.rest.client.annotation.RegisterProvider
annotations:
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;
@RegisterRestClient
@OidcClientFilter
@Path("/")
public interface ProtectedResourceService {
@GET
Uni<String> getUserName();
}
or
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter;
import io.smallrye.mutiny.Uni;
@RegisterRestClient
@RegisterProvider(OidcClientRequestReactiveFilter.class)
@Path("/")
public interface ProtectedResourceService {
@GET
Uni<String> getUserName();
}
OidcClientRequestReactiveFilter
uses a default OidcClient
by default. A named OidcClient
can be selected with a quarkus.oidc-client-reactive-filter.client-name
configuration property.
You can also select OidcClient
by setting the value
attribute of the @OidcClientFilter
annotation. The client name set through annotation has higher priority than the quarkus.oidc-client-reactive-filter.client-name
configuration property.
For example, given this jwt-secret
named OIDC client declaration, you can refer to this client like this:
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;
@RegisterRestClient
@OidcClientFilter("jwt-secret")
@Path("/")
public interface ProtectedResourceService {
@GET
Uni<String> getUserName();
}
RestClient の ClientFilter で OidcClient を使用する
以下の Maven 依存関係を追加します。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client-filter</artifactId>
</dependency>
また、 io.quarkus:quarkus-oidc-client
も持ってくることに注意しましょう。
quarkus-oidc-client-filter
エクステンションは、io.quarkus.oidc.client.filter.OidcClientRequestFilter
Jakarta REST ClientRequestFilter を提供し、 OidcClient
を使用してアクセストークンを取得し、必要に応じてリフレッシュし、HTTP Authorization
Bearer
スキーム値として設定します。
By default, this filter will get OidcClient
to acquire the first pair of access and refresh tokens at its initialization time. If the access tokens are short-lived and refresh tokens are unavailable, then the token acquisition should be delayed with quarkus.oidc-client.early-tokens-acquisition=false
.
io.quarkus.oidc.client.filter.OidcClientFilter
または org.eclipse.microprofile.rest.client.annotation.RegisterProvider
アノテーションを使用して OidcClientRequestFilter
を選択的に登録することができます。
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
@RegisterRestClient
@OidcClientFilter
@Path("/")
public interface ProtectedResourceService {
@GET
String getUserName();
}
or
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientRequestFilter;
@RegisterRestClient
@RegisterProvider(OidcClientRequestFilter.class)
@Path("/")
public interface ProtectedResourceService {
@GET
String getUserName();
}
Alternatively, OidcClientRequestFilter
can be registered automatically with all MP Rest or Jakarta REST clients if the quarkus.oidc-client-filter.register-filter=true
property is set.
OidcClientRequestFilter
uses a default OidcClient
by default. A named OidcClient
can be selected with a quarkus.oidc-client-filter.client-name
configuration property.
You can also select OidcClient
by setting the value
attribute of the @OidcClientFilter
annotation. The client name set through annotation has higher priority than the quarkus.oidc-client-filter.client-name
configuration property.
For example, given this jwt-secret
named OIDC client declaration, you can refer to this client like this:
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
@RegisterRestClient
@OidcClientFilter("jwt-secret")
@Path("/")
public interface ProtectedResourceService {
@GET
String getUserName();
}
Use a custom RestClient ClientFilter
If you prefer, you can use your own custom filter and inject Tokens
:
import io.quarkus.oidc.client.Tokens;
@Provider
@Priority(Priorities.AUTHENTICATION)
public class OidcClientRequestCustomFilter implements ClientRequestFilter {
@Inject
Tokens tokens;
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + tokens.getAccessToken());
}
}
Tokens
プロデューサーがトークンを取得・更新し、カスタムフィルターが何時、どのようにトークンを使用するかを決定します。
名前付きの Tokens
を挿入することもできます。Inject named OidcClient and Tokens を参照してください。
Refreshing access tokens
OidcClientRequestReactiveFilter
, OidcClientRequestFilter
and Tokens
producers will refresh the current expired access token if the refresh token is available.
Additionally, the quarkus.oidc-client.refresh-token-time-skew
property can be used for a preemptive access token refreshment to avoid sending nearly expired access tokens that might cause HTTP 401 errors. For example, if this property is set to 3S
and the access token will expire in less than 3 seconds, then this token will be auto-refreshed.
If the access token needs to be refreshed, but no refresh token is available, then an attempt is made to acquire a new token by using a configured grant, such as client_credentials
.
Some OpenID Connect Providers will not return a refresh token in a client_credentials
grant response. For example, starting from Keycloak 12, a refresh token will not be returned by default for client_credentials
. The providers might also restrict the number of times a refresh token can be used.
Revoking access tokens
If your OpenId Connect provider, such as Keycloak, supports a token revocation endpoint, then OidcClient#revokeAccessToken
can be used to revoke the current access token. The revocation endpoint URL will be discovered alongside the token request URI or can be configured with quarkus.oidc-client.revoke-path
.
You might want to have the access token revoked if using this token with a REST client fails with an HTTP 401
status code or if the access token has already been used for a long time and you would like to refresh it.
This can be achieved by requesting a token refresh by using a refresh token. However, if the refresh token is unavailable, you can refresh it by revoking it first and then requesting a new access token.
OidcClient authentication
OidcClient
は、client_credentials
および他のグラントリクエストが成功するために OpenID Connect プロバイダーに対して認証する必要があります。https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication[OIDC Client Authentication] オプションはすべてサポートされています。以下に例を示します。
client_secret_basic
.
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=mysecret
or
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.client-secret.value=mysecret
Or with the secret retrieved from a CredentialsProvider:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
# This key is used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc-client.credentials.client-secret.provider.key=mysecret-key
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc-client.credentials.client-secret.provider.name=oidc-credentials-provider
client_secret_post
:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.client-secret.value=mysecret
quarkus.oidc-client.credentials.client-secret.method=post
client_secret_jwt
、署名アルゴリズムは HS256
です。
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
Or with the secret retrieved from a CredentialsProvider, signature algorithm is HS256
:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
# This is a key that will be used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc-client.credentials.jwt.secret-provider.key=mysecret-key
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc-client.credentials.jwt.secret-provider.name=oidc-credentials-provider
PEM キーファイルを使用した private_key_jwt
、署名アルゴリズムは RS256
です。
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.key-file=privateKey.pem
private_key_jwt
with the keystore file, signature algorithm is RS256
:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.key-store-file=keystore.jks
quarkus.oidc-client.credentials.jwt.key-store-password=mypassword
quarkus.oidc-client.credentials.jwt.key-password=mykeypassword
# Private key alias inside the keystore
quarkus.oidc-client.credentials.jwt.key-id=mykeyAlias
client_secret_jwt
または private_key_jwt
認証方法を使用することで、クライアントシークレットが漏れることはありません。
JWT認証の追加オプション
If either client_secret_jwt
or private_key_jwt
authentication methods are used, then the JWT signature algorithm, key identifier, audience, subject, and issuer can be customized, for example:
# private_key_jwt client authentication
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.key-file=privateKey.pem
# This is a token key identifier 'kid' header - set it if your OpenID Connect provider requires it.
# Note that if the key is represented in a JSON Web Key (JWK) format with a `kid` property, then
# using 'quarkus.oidc-client.credentials.jwt.token-key-id' is unnecessary.
quarkus.oidc-client.credentials.jwt.token-key-id=mykey
# Use the RS512 signature algorithm instead of the default RS256
quarkus.oidc-client.credentials.jwt.signature-algorithm=RS512
# The token endpoint URL is the default audience value; use the base address URL instead:
quarkus.oidc-client.credentials.jwt.audience=${quarkus.oidc-client.auth-server-url}
# custom subject instead of the client ID:
quarkus.oidc-client.credentials.jwt.subject=custom-subject
# custom issuer instead of the client ID:
quarkus.oidc-client.credentials.jwt.issuer=custom-issuer
Apple POST JWT
Apple OpenID Connect Provider uses a client_secret_post
method where a secret is a JWT produced with a private_key_jwt
authentication method but with Apple account-specific issuer and subject properties.
quarkus-oidc-client
supports a non-standard client_secret_post_jwt
authentication method, which can be configured as follows:
quarkus.oidc-client.auth-server-url=${apple.url}
quarkus.oidc-client.client-id=${apple.client-id}
quarkus.oidc-client.credentials.client-secret.method=post-jwt
quarkus.oidc-client.credentials.jwt.key-file=ecPrivateKey.pem
quarkus.oidc-client.credentials.jwt.signature-algorithm=ES256
quarkus.oidc-client.credentials.jwt.subject=${apple.subject}
quarkus.oidc-client.credentials.jwt.issuer=${apple.issuer}
相互 TLS
Some OpenID Connect Providers require that a client is authenticated as part of the mutual TLS (mTLS
) authentication process.
mTLS
をサポートする為に quarkus-oidc-client
は以下のように設定出来ます :
quarkus.oidc-client.tls.verification=certificate-validation
# Keystore configuration
quarkus.oidc-client.tls.key-store-file=client-keystore.jks
quarkus.oidc-client.tls.key-store-password=${key-store-password}
# Add more keystore properties if needed:
#quarkus.oidc-client.tls.key-store-alias=keyAlias
#quarkus.oidc-client.tls.key-store-alias-password=keyAliasPassword
# Truststore configuration
quarkus.oidc-client.tls.trust-store-file=client-truststore.jks
quarkus.oidc-client.tls.trust-store-password=${trust-store-password}
# Add more truststore properties if needed:
#quarkus.oidc-client.tls.trust-store-alias=certAlias
テスト
テストプロジェクトに以下の依存関係を追加することから始めます。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
Wiremock
テストプロジェクトに以下の依存関係を追加します。
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
</dependency>
Write a Wiremock-based QuarkusTestResourceLifecycleManager
, for example:
package io.quarkus.it.keycloak;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import java.util.HashMap;
import java.util.Map;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.Options.ChunkedEncodingPolicy;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
public class KeycloakRealmResourceManager implements QuarkusTestResourceLifecycleManager {
private WireMockServer server;
@Override
public Map<String, String> start() {
server = new WireMockServer(wireMockConfig().dynamicPort().useChunkedTransferEncoding(ChunkedEncodingPolicy.NEVER));
server.start();
server.stubFor(WireMock.post("/tokens")
.withRequestBody(matching("grant_type=password&username=alice&password=alice"))
.willReturn(WireMock
.aResponse()
.withHeader("Content-Type", "application/json")
.withBody(
"{\"access_token\":\"access_token_1\", \"expires_in\":4, \"refresh_token\":\"refresh_token_1\"}")));
server.stubFor(WireMock.post("/tokens")
.withRequestBody(matching("grant_type=refresh_token&refresh_token=refresh_token_1"))
.willReturn(WireMock
.aResponse()
.withHeader("Content-Type", "application/json")
.withBody(
"{\"access_token\":\"access_token_2\", \"expires_in\":4, \"refresh_token\":\"refresh_token_1\"}")));
Map<String, String> conf = new HashMap<>();
conf.put("keycloak.url", server.baseUrl());
return conf;
}
@Override
public synchronized void stop() {
if (server != null) {
server.stop();
server = null;
}
}
}
Prepare the REST test endpoints. You can have the test front-end endpoint, which uses the injected MP REST client with a registered OidcClient filter, call the downstream endpoint. This endpoint echoes the token back. For example, see the integration-tests/oidc-client-wiremock
in the main
Quarkus repository.
application.properties
を次のように設定します。
# Use the 'keycloak.url' property set by the test KeycloakRealmResourceManager
quarkus.oidc-client.auth-server-url=${keycloak.url}
quarkus.oidc-client.discovery-enabled=false
quarkus.oidc-client.token-path=/tokens
quarkus.oidc-client.client-id=quarkus-service-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=password
quarkus.oidc-client.grant-options.password.username=alice
quarkus.oidc-client.grant-options.password.password=alice
And finally, write the test code. Given the Wiremock-based resource above, the first test invocation should return the access_token_1
access token, which will expire in 4 seconds. Use awaitility
to wait for about 5 seconds, and now the next test invocation should return the access_token_2
access token, which confirms the expired access_token_1
access token has been refreshed.
Keycloak
If you work with Keycloak, you can use the same approach described in the OpenID Connect Bearer Token Integration testing Keycloak section.
ログでエラーを確認する方法
Enable io.quarkus.oidc.client.runtime.OidcClientImpl
TRACE
level logging to see more details about the token acquisition and refresh errors:
quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".level=TRACE
quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".min-level=TRACE
Enable io.quarkus.oidc.client.runtime.OidcClientRecorder
TRACE
level logging to see more details about the OidcClient initialization errors:
quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientRecorder".level=TRACE
quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientRecorder".min-level=TRACE
OIDC request filters
You can filter OIDC requests made by Quarkus to the OIDC provider by registering one or more OidcRequestFilter
implementations, which can update or add new request headers. For example, a filter can analyze the request body and add its digest as a new header value:
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.core.http.HttpMethod;
import io.vertx.mutiny.core.buffer.Buffer;
import io.vertx.mutiny.ext.web.client.HttpRequest;
@ApplicationScoped
@Unremovable
public class OidcRequestCustomizer implements OidcRequestFilter {
@Override
public void filter(HttpRequest<Buffer> request, Buffer buffer, OidcRequestContextProperties contextProperties) {
HttpMethod method = request.method();
String uri = request.uri();
if (method == HttpMethod.POST && uri.endsWith("/service") && buffer != null) {
request.putHeader("Digest", calculateDigest(buffer.toString()));
}
}
private String calculateDigest(String bodyString) {
// Apply the required digest algorithm to the body string
}
}
Token Propagation Reactive
The quarkus-oidc-token-propagation-reactive
extension provides a RestEasy Reactive Client, io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter
, that simplifies the propagation of authentication information. This client propagates the bearer token present in the currently active request or the token acquired from the authorization code flow mechanism as the HTTP Authorization
header’s Bearer
scheme value.
io.quarkus.oidc.token.propagation.AccessToken
または org.eclipse.microprofile.rest.client.annotation.RegisterProvider
のいずれかを使用して、 AccessTokenRequestFilter
を選択的に登録できます。例:
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.AccessToken;
@RegisterRestClient
@AccessToken
@Path("/")
public interface ProtectedResourceService {
@GET
String getUserName();
}
or
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter;
@RegisterRestClient
@RegisterProvider(AccessTokenRequestReactiveFilter.class)
@Path("/")
public interface ProtectedResourceService {
@GET
String getUserName();
}
さらに、 AccessTokenRequestReactiveFilter
、トークンを伝播する前に交換する必要がある複雑なアプリケーションをサポートすることができます。
If you work with Keycloak or another OIDC provider that supports a Token Exchange token grant, then you can configure AccessTokenRequestReactiveFilter
to exchange the token like this:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=exchange
quarkus.oidc-client.grant-options.exchange.audience=quarkus-app-exchange
quarkus.oidc-token-propagation.exchange-token=true
AccessTokenRequestReactiveFilter
は OidcClient
を使用して現在のトークンを交換することに注意してください。また、quarkus.oidc-client.grant-options.exchange
を使用して OpenID Connect プロバイダーが期待する追加の交換プロパティーを設定できます。
If you work with providers such as Azure
that require using JWT bearer token grant to exchange the current token, then you can configure AccessTokenRequestReactiveFilter
to exchange the token like this:
quarkus.oidc-client.auth-server-url=${azure.provider.url}
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=jwt
quarkus.oidc-client.grant-options.jwt.requested_token_use=on_behalf_of
quarkus.oidc-client.scopes=https://graph.microsoft.com/user.read,offline_access
quarkus.oidc-token-propagation-reactive.exchange-token=true
AccessTokenRequestReactiveFilter
はデフォルトでデフォルトの OidcClient
を使用します。名前付きの OidcClient
は quarkus.oidc-token-propagation-reactive.client-name
設定プロパティーで選択することができます。
トークンの伝播
The quarkus-oidc-token-propagation
extension provides two Jakarta REST jakarta.ws.rs.client.ClientRequestFilter
class implementations that simplify the propagation of authentication information.
io.quarkus.oidc.token.propagation.AccessTokenRequestFilter
propagates the Bearer token present in the current active request or the token acquired from the Authorization code flow mechanism, as the HTTP Authorization
header’s Bearer
scheme value.
The io.quarkus.oidc.token.propagation.JsonWebTokenRequestFilter
provides the same functionality but, in addition, provides support for JWT tokens.
When you need to propagate the current Authorization Code Flow access token, then the immediate token propagation will work well - as the code flow access tokens (as opposed to ID tokens) are meant to be propagated for the current Quarkus endpoint to access the remote services on behalf of the currently authenticated user.
However, the direct end-to-end Bearer token propagation should be avoided. For example, Client → Service A → Service B
where Service B
receives a token sent by Client
to Service A
. In such cases, Service B
cannot distinguish if the token came from Service A
or from Client
directly. For Service B
to verify the token came from Service A
, it should be able to assert a new issuer and audience claims.
Additionally, a complex application might need to exchange or update the tokens before propagating them. For example, the access context might be different when Service A
is accessing Service B
. In this case, Service A
might be granted a narrow or completely different set of scopes to access Service B
.
以下のセクションでは、AccessTokenRequestFilter
と JsonWebTokenRequestFilter
がどのように役立つかを説明します。
RestClient AccessTokenRequestFilter
AccessTokenRequestFilter
treats all tokens as Strings, and as such, it can work with both JWT and opaque tokens.
io.quarkus.oidc.token.propagation.AccessToken
または org.eclipse.microprofile.rest.client.annotation.RegisterProvider
のいずれかを使用して、 AccessTokenRequestFilter
を選択的に登録できます。例:
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.AccessToken;
@RegisterRestClient
@AccessToken
@Path("/")
public interface ProtectedResourceService {
@GET
String getUserName();
}
or
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.AccessTokenRequestFilter;
@RegisterRestClient
@RegisterProvider(AccessTokenRequestFilter.class)
@Path("/")
public interface ProtectedResourceService {
@GET
String getUserName();
}
Alternatively, AccessTokenRequestFilter
can be registered automatically with all MP Rest or Jakarta REST clients if the quarkus.oidc-token-propagation.register-filter
property is set to true
and quarkus.oidc-token-propagation.json-web-token
property is set to false
(which is a default value).
Exchange token before propagation
If the current access token needs to be exchanged before propagation and you work with Keycloak or other OpenID Connect Provider which supports a Token Exchange token grant, then you can configure AccessTokenRequestFilter
like this:
quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=exchange
quarkus.oidc-client.grant-options.exchange.audience=quarkus-app-exchange
quarkus.oidc-token-propagation.exchange-token=true
If you work with providers such as Azure
that require using JWT bearer token grant to exchange the current token, then you can configure AccessTokenRequestFilter
to exchange the token like this:
quarkus.oidc-client.auth-server-url=${azure.provider.url}
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=jwt
quarkus.oidc-client.grant-options.jwt.requested_token_use=on_behalf_of
quarkus.oidc-client.scopes=https://graph.microsoft.com/user.read,offline_access
quarkus.oidc-token-propagation.exchange-token=true
AccessTokenRequestFilter
は OidcClient
を使用して現在のトークンを交換することに注意してください。また、quarkus.oidc-client.grant-options.exchange
を使用して OpenID Connect プロバイダーが期待する追加の交換プロパティーを設定できます。
AccessTokenRequestFilter
はデフォルトで OidcClient
を使用します。名前付きの OidcClient
は quarkus.oidc-token-propagation.client-name
設定プロパティーで選択することができます。
RestClient JsonWebTokenRequestFilter
Using JsonWebTokenRequestFilter
is recommended if you work with Bearer JWT tokens where these tokens can have their claims, such as issuer
and audience
modified and the updated tokens secured (for example, re-signed) again. It expects an injected org.eclipse.microprofile.jwt.JsonWebToken
and, therefore, will not work with the opaque tokens. Also, if your OpenID Connect Provider supports a Token Exchange protocol, then it is recommended to use AccessTokenRequestFilter
instead - as both JWT and opaque bearer tokens can be securely exchanged with AccessTokenRequestFilter
.
JsonWebTokenRequestFilter
makes it easy for Service A
implementations to update the injected org.eclipse.microprofile.jwt.JsonWebToken
with the new issuer
and audience
claim values and secure the updated token again with a new signature. The only difficult step is ensuring that Service A
has a signing key; it should be provisioned from a secure file system or remote secure storage such as Vault.
たとえば、 io.quarkus.oidc.token.propagation.JsonWebToken
または org.eclipse.microprofile.rest.client.annotation.RegisterProvider
を使用して、 JsonWebTokenRequestFilter
を選択的に登録することが可能です。
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.JsonWebToken;
@RegisterRestClient
@JsonWebToken
@Path("/")
public interface ProtectedResourceService {
@GET
String getUserName();
}
or
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.JsonWebTokenRequestFilter;
@RegisterRestClient
@RegisterProvider(JsonWebTokenRequestFilter.class)
@Path("/")
public interface ProtectedResourceService {
@GET
String getUserName();
}
あるいは、 quarkus.oidc-token-propagation.register-filter
と quarkus.oidc-token-propagation.json-web-token
の両方のプロパティを true
に設定すると、すべての MicroProfile REST または Jakarta REST クライアントで JsonWebTokenRequestFilter
が自動的に登録されます。
Update token before propagation
If the injected token needs to have its iss
(issuer) or aud
(audience) claims updated and secured again with a new signature, then you can configure JsonWebTokenRequestFilter
like this:
quarkus.oidc-token-propagation.secure-json-web-token=true
smallrye.jwt.sign.key.location=/privateKey.pem
# Set a new issuer
smallrye.jwt.new-token.issuer=http://frontend-resource
# Set a new audience
smallrye.jwt.new-token.audience=http://downstream-resource
# Override the existing token issuer and audience claims if they are already set
smallrye.jwt.new-token.override-matching-claims=true
As mentioned, use AccessTokenRequestFilter
if you work with Keycloak or an OpenID Connect Provider that supports a Token Exchange protocol.
テスト
You can generate the tokens as described in OpenID Connect Bearer Token Integration testing section.
Prepare the REST test endpoints. You can have the test front-end endpoint, which uses the injected MP REST client with a registered token propagation filter, call the downstream endpoint. For example, see the integration-tests/oidc-token-propagation
in the main
Quarkus repository.
Token Propagation Reactive
以下の Maven 依存関係を追加します。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-token-propagation-reactive</artifactId>
</dependency>
quarkus-oidc-token-propagation-reactive
エクステンションは、io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter
を提供します。これは、現在の Bearer
または Authorization Code Flow
アクセストークンを伝播するために使用することができます。
The quarkus-oidc-token-propagation-reactive
extension (as opposed to the non-reactive quarkus-oidc-token-propagation
extension) does not currently support the exchanging or resigning of the tokens before the propagation.
However, these features might be added in the future.
GraphQL client integration
The quarkus-oidc-client-graphql
extension provides a way to integrate an OIDC client into GraphQL clients paralleling the approach used with REST clients.
When this extension is active, any GraphQL client configured through properties (rather than programmatically by the builder) will use the OIDC client to acquire an access token, which it will then set as the Authorization
header value.
The OIDC client will also refresh expired access tokens.
To configure which OIDC client should be used by the GraphQL client, select one of the configured OIDC clients with the quarkus.oidc-client-graphql.client-name
property, for example:
quarkus.oidc-client-graphql.client-name=oidc-client-for-graphql # example declaration of the OIDC client itself quarkus.oidc-client.oidc-client-for-graphql.auth-server-url=${keycloak.url} quarkus.oidc-client.oidc-client-for-graphql.grant.type=password quarkus.oidc-client.oidc-client-for-graphql.grant-options.password.username=${username} quarkus.oidc-client.oidc-client-for-graphql.grant-options.password.password=${password} quarkus.oidc-client.oidc-client-for-graphql.client-id=${quarkus.oidc.client-id} quarkus.oidc-client.oidc-client-for-graphql.credentials.client-secret.value=${keycloak.credentials.secret} quarkus.oidc-client.oidc-client-for-graphql.credentials.client-secret.method=POST
If you don’t specify the quarkus.oidc-client-graphql.client-name property,
GraphQL clients will use the default OIDC client (without an explicit name).
|
Specifically for type-safe GraphQL clients, you can override this on a
per-client basis by annotating the GraphQLClientApi
interface with
@io.quarkus.oidc.client.filter.OidcClientFilter
. For example:
@GraphQLClientApi(configKey = "order-client")
@OidcClientFilter("oidc-client-for-graphql")
public interface OrdersGraphQLClient {
// Queries, mutations, and subscriptions go here.
}
To be able to use this with a programmatically created GraphQL client, both
builders (VertxDynamicGraphQLClientBuilder
and
VertxTypesafeGraphQLClientBuilder
) contain a method dynamicHeader(String,
Uni<String>
) that allows you to plug in a header that might change for
every request. To plug an OIDC client into it, use
@Inject
OidcClients oidcClients;
VertxTypesafeGraphQLClientBuilder builder = ....;
Uni<String> tokenUni = oidcClients.getClient("OIDC_CLIENT_NAME")
.getTokens().map(t -> "Bearer " + t.getAccessToken());
builder.dynamicHeader("Authorization", tokenUni);
VertxDynamicGraphQLClient client = builder.build();