diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index 8052f7ac76c5c..253ae2b5132ff 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -238,9 +238,9 @@ You can inject `Tokens` that use `OidcClient` internally. `Tokens` can be used t [source,java] ---- -import jakarta.inject.PostConstruct; import jakarta.inject.Inject; import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import io.quarkus.oidc.client.Tokens; @@ -281,6 +281,7 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import io.smallrye.mutiny.Uni; import io.quarkus.oidc.client.OidcClient; import io.quarkus.oidc.client.OidcClients; @@ -404,6 +405,7 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import io.smallrye.mutiny.Uni; import io.quarkus.oidc.client.runtime.TokensHelper; @@ -442,7 +444,10 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import io.smallrye.mutiny.Uni; +import io.quarkus.oidc.client.NamedOidcClient; +import io.quarkus.oidc.client.OidcClient; import io.quarkus.oidc.client.runtime.TokensHelper; @Path("/clients") @@ -473,6 +478,20 @@ The same qualifier can be used to specify the `OidcClient` used for a `Tokens` i [source,java] ---- +import java.io.IOException; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.ext.Provider; + +import io.quarkus.oidc.client.NamedOidcClient; +import io.quarkus.oidc.client.Tokens; + @Provider @Priority(Priorities.AUTHENTICATION) @RequestScoped @@ -502,11 +521,11 @@ Add the following Maven Dependency: ---- -Note it will also bring `io.quarkus:quarkus-oidc-client`. +NOTE: It will also bring `io.quarkus:quarkus-oidc-client`. `quarkus-rest-client-oidc-filter` extension provides `io.quarkus.oidc.client.filter.OidcClientRequestReactiveFilter`. -It works similarly to the way `OidcClientRequestFilter` does (see <>) - 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 xref:rest-client.adoc[Reactive RestClient] and implements a non-blocking client filter that does not block the current IO thread when acquiring or refreshing the tokens. +It works similarly to the way `OidcClientRequestFilter` does (see <>) - 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 xref:rest-client.adoc[Reactive RestClient] and implements a non-blocking client filter that does not block the current IO thread when acquiring or refreshing the tokens. `OidcClientRequestReactiveFilter` delays an initial token acquisition until it is executed to avoid blocking an IO thread. @@ -514,10 +533,11 @@ You can selectively register `OidcClientRequestReactiveFilter` by using either ` [source,java] ---- -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; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @OidcClientFilter @@ -537,6 +557,8 @@ 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; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @RegisterProvider(OidcClientRequestReactiveFilter.class) @@ -554,10 +576,11 @@ For example, given <> `jwt-secret` named OIDC client decl [source,java] ---- -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; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @OidcClientFilter("jwt-secret") @@ -582,9 +605,9 @@ Add the following Maven Dependency: ---- -Note it will also bring `io.quarkus:quarkus-oidc-client`. +NOTE: It will also bring `io.quarkus:quarkus-oidc-client`. -`quarkus-resteasy-client-oidc-filter` extension provides `io.quarkus.oidc.client.filter.OidcClientRequestFilter` Jakarta REST ClientRequestFilter which uses `OidcClient` to acquire the access token, refresh it if needed, and set it as an HTTP `Authorization` `Bearer` scheme value. +`quarkus-resteasy-client-oidc-filter` extension provides `io.quarkus.oidc.client.filter.OidcClientRequestFilter` Jakarta REST ClientRequestFilter which uses `OidcClient` to acquire the access token, refresh it if needed, and set it as an HTTP `Authorization Bearer` scheme value. 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`. @@ -594,6 +617,8 @@ You can selectively register `OidcClientRequestFilter` by using either `io.quark ---- import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.filter.OidcClientFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @OidcClientFilter @@ -612,6 +637,8 @@ or import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.filter.OidcClientRequestFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @RegisterProvider(OidcClientRequestFilter.class) @@ -633,6 +660,8 @@ For example, given <> `jwt-secret` named OIDC client decl ---- import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.filter.OidcClientFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @OidcClientFilter("jwt-secret") @@ -650,6 +679,14 @@ If you prefer, you can use your own custom filter and inject `Tokens`: [source,java] ---- +import java.io.IOException; +import jakarta.annotation.Priority; +import jakarta.inject.Inject; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.ext.Provider; import io.quarkus.oidc.client.Tokens; @Provider @@ -1003,8 +1040,10 @@ Add the following dependencies to your test project: org.wiremock wiremock test + ${wiremock.version} // <1> ---- +<1> Use a proper Wiremock version. All available versions can be found link:https://search.maven.org/artifact/org.wiremock/wiremock[here]. Write a Wiremock-based `QuarkusTestResourceLifecycleManager`, for example: [source, java] @@ -1200,6 +1239,8 @@ You can selectively register `AccessTokenRequestReactiveFilter` by using either ---- import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.AccessToken; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @AccessToken @@ -1218,6 +1259,8 @@ 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; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @RegisterProvider(AccessTokenRequestReactiveFilter.class) @@ -1246,7 +1289,7 @@ quarkus.resteasy-client-oidc-token-propagation.exchange-token=true <1> ---- <1> Please note that the `exchange-token` configuration property is ignored when the OidcClient name is set with the `io.quarkus.oidc.token.propagation.AccessToken#exchangeTokenClient` annotation attribute. -Note `AccessTokenRequestReactiveFilter` will use `OidcClient` to exchange the current token, and you can use `quarkus.oidc-client.grant-options.exchange` to set the additional exchange properties expected by your OpenID Connect Provider. +NOTE: `AccessTokenRequestReactiveFilter` will use `OidcClient` to exchange the current token, and you can use `quarkus.oidc-client.grant-options.exchange` to set the additional exchange properties expected by your OpenID Connect Provider. If you work with providers such as `Azure` that link:https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#example[require using] link:https://www.rfc-editor.org/rfc/rfc7523#section-2.1[JWT bearer token grant] to exchange the current token, then you can configure `AccessTokenRequestReactiveFilter` to exchange the token like this: @@ -1290,6 +1333,8 @@ You can selectively register `AccessTokenRequestFilter` by using either `io.quar ---- import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.AccessToken; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @AccessToken @@ -1307,6 +1352,8 @@ or import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.AccessTokenRequestFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @RegisterProvider(AccessTokenRequestFilter.class) @@ -1350,7 +1397,7 @@ quarkus.oidc-client.scopes=https://graph.microsoft.com/user.read,offline_access quarkus.resteasy-client-oidc-token-propagation.exchange-token=true ---- -Note `AccessTokenRequestFilter` will use `OidcClient` to exchange the current token, and you can use `quarkus.oidc-client.grant-options.exchange` to set the additional exchange properties expected by your OpenID Connect Provider. +NOTE: `AccessTokenRequestFilter` will use `OidcClient` to exchange the current token, and you can use `quarkus.oidc-client.grant-options.exchange` to set the additional exchange properties expected by your OpenID Connect Provider. `AccessTokenRequestFilter` uses a default `OidcClient` by default. A named `OidcClient` can be selected with a `quarkus.resteasy-client-oidc-token-propagation.client-name` configuration property. @@ -1358,7 +1405,7 @@ Note `AccessTokenRequestFilter` will use `OidcClient` to exchange the current to 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. +`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 which should be provisioned from a secure file system or remote secure storage such as Vault. You can selectively register `JsonWebTokenRequestFilter` by using either `io.quarkus.oidc.token.propagation.JsonWebToken` or `org.eclipse.microprofile.rest.client.annotation.RegisterProvider`, for example: @@ -1366,6 +1413,8 @@ You can selectively register `JsonWebTokenRequestFilter` by using either `io.qua ---- import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.JsonWebToken; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @JsonWebToken @@ -1383,6 +1432,8 @@ or import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.JsonWebTokenRequestFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @RegisterProvider(JsonWebTokenRequestFilter.class) diff --git a/docs/src/main/asciidoc/security-openid-connect-client.adoc b/docs/src/main/asciidoc/security-openid-connect-client.adoc index 318d963c1177b..134a70af24368 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client.adoc @@ -172,7 +172,6 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; -import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.filter.OidcClientFilter; @@ -237,7 +236,6 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; -import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.AccessToken; @@ -446,7 +444,7 @@ public class FrontendExceptionMapper implements ExceptionMapper> section. @@ -530,18 +528,18 @@ include::{includes}/devtools/dev.adoc[] xref:security-openid-connect-dev-services.adoc[Dev Services for Keycloak] launches a Keycloak container and imports `quarkus-realm.json`. -Open a xref:dev-ui.adoc[Dev UI] available at http://localhost:8080/q/dev-ui[/q/dev-ui] and click a `Provider: Keycloak` link in the *OpenID Connect Dev UI* card. +Open a xref:dev-ui.adoc[Dev UI] available at http://localhost:8080/q/dev-ui[/q/dev-ui] and click a `Keycloak provider` link in the *OpenID Connect Dev UI* card. When asked, log in to a `Single Page Application` provided by the OpenID Connect Dev UI: * Log in as `alice`, with the password, `alice`. -This user has a `user` role. - ** Access `/frontend/user-name-with-propagated-token`, which returns `200`. - ** Access `/frontend/admin-name-with-propagated-token`, which returns `403`. - * Log out and back in as `admin` with the password, `admin`. This user has both `admin` and `user` roles. ** Access `/frontend/user-name-with-propagated-token`, which returns `200`. ** Access `/frontend/admin-name-with-propagated-token`, which returns `200`. + * Log out and back in as `bob` with the password, `bob`. +This user has a `user` role. + ** Access `/frontend/user-name-with-propagated-token`, which returns `200`. + ** Access `/frontend/admin-name-with-propagated-token`, which returns `403`. You have tested that `FrontendResource` can propagate the access tokens from the OpenID Connect Dev UI. @@ -681,7 +679,7 @@ Call the `/admin-name-with-oidc-client-token-header-param`. In contrast with the [source,bash] ---- curl -i -X GET \ - http://localhost:8080/frontend/admin-name-with-oidc-client-token-param + http://localhost:8080/frontend/admin-name-with-oidc-client-token-header-param ---- Next, test the endpoints which use OIDC client in in the blocking mode. @@ -699,7 +697,7 @@ Call the `/admin-name-with-oidc-client-token-header-param-blocking`. In contrast [source,bash] ---- curl -i -X GET \ - http://localhost:8080/frontend/admin-name-with-oidc-client-token-param-blocking + http://localhost:8080/frontend/admin-name-with-oidc-client-token-header-param-blocking ---- == References