OpenID Connect (OIDC) and OAuth2 client and filters
您可以使用 Quarkus 扩展来进行 OpenID Connect 和 OAuth 2.0 访问令牌管理,重点在于获取、刷新和传播令牌。
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-rest-client-oidc-filter
andquarkus-resteasy-client-oidc-filter
extensions to acquire and refresh access tokens from OpenID Connect and OAuth 2.0 compliant Authorization Servers such as Keycloak. -
Using
quarkus-rest-client-oidc-token-propagation
andquarkus-resteasy-client-oidc-token-propagation
extensions to propagate the currentBearer
orAuthorization Code Flow
access tokens.
这些扩展管理的访问令牌可以用作 HTTP Authorization Bearer 令牌来访问远程服务。
The access tokens managed by these extensions can be used as HTTP Authorization Bearer tokens to access the remote services.
OidcClient
添加以下依赖项:
Add the following dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-client</artifactId>
</dependency>
quarkus-oidc-client
扩展提供了一个反应式 io.quarkus.oidc.client.OidcClient
,该反应式可用于通过 SmallRye Mutiny Uni
和 Vert.x WebClient
来获取和刷新令牌。
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
在构建时通过 IDP 令牌端点 URL 初始化,该 URL 可以自动发现或手动配置。OidcClient
使用此端点通过令牌授权(如 client_credentials
或 password
)获取访问令牌,并通过 refresh_token
授权刷新令牌。
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
路径来发现。
By default, the token endpoint address is discovered by adding a /.well-known/openid-configuration
path to the configured quarkus.oidc-client.auth-server-url
.
例如,给定此 Keycloak URL:
For example, given this 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
。
OidcClient
will discover that the token endpoint URL is 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:
A more compact way to configure the token endpoint URL without the discovery is to set quarkus.oidc-client.token-path
to an absolute URL:
quarkus.oidc-client.token-path=http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/tokens
在这种情况下,无需设置 quarkus.oidc-client.auth-server-url
和 quarkus.oidc-client.discovery-enabled
。
Setting quarkus.oidc-client.auth-server-url
and quarkus.oidc-client.discovery-enabled
is not required in this case.
Supported token grants
OidcClient
用于获取令牌的主要令牌授权是 client_credentials
(默认)和 password
授权。
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
授权:
Here is how OidcClient
can be configured to use the client_credentials
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
client_credentials
授权允许通过使用 quarkus.oidc-client.grant-options.client.<param-name>=<value>
来设置令牌请求的额外参数。以下是使用 audience
参数设置预期令牌接收者的方法:
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
授权:
Here is how OidcClient
can be configured to use the password
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=password
quarkus.oidc-client.grant-options.password.username=alice
quarkus.oidc-client.grant-options.password.password=alice
它可以通过使用 quarkus.oidc-client.grant-options.password
配置前缀进行进一步自定义,类似于可以自定义客户端凭证授权的方式。
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
还可以帮助通过使用需要一些配置中无法捕获的额外输入参数的授权来获取令牌。这些授权为 refresh_token
(带外部刷新令牌)、 authorization_code`以及可用于交换当前访问令牌的两个授权,即 `urn:ietf:params:oauth:grant-type:token-exchange
和 urn:ietf:params:oauth:grant-type:jwt-bearer
。
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
.
如果你需要获取访问令牌并将现有刷新令牌发布到当前 Quarkus 端点,你必须使用 refresh_token
授权。此授权使用带外刷新令牌来获取新的令牌集。在这种情况下,请按如下方式配置 OidcClient
:
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
然后,你可以将 OidcClient.refreshTokens
方法与提供的刷新令牌一起使用以获取访问令牌。
Then you can use the OidcClient.refreshTokens
method with a provided refresh token to get the access token.
如果你正在构建一个复杂的微服务应用程序并在多个服务中使用同一个 Bearer`令牌,则可能需要使用 `urn:ietf:params:oauth:grant-type:token-exchange
或 urn:ietf:params:oauth:grant-type:jwt-bearer
授权。有关更多详细信息,请参阅 Token Propagation in MicroProfile RestClient Reactive filter 和 Token Propagation in MicroProfile RestClient filter。
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-reactive and token-propagation for more details.
如果你由于某种原因无法使用 Quarkus OIDC extension 来支持授权代码流程,则可能需要使用 OidcClient
来支持 authorization code
授权。如果你有非常充分的理由来实现授权代码流程,则可以按如下方式配置 OidcClient
:
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
然后,可以使用 OidcClient.accessTokens
方法来接受额外属性的 Map,并传递当前 code
和 redirect_uri
参数以交换授权代码以获取令牌。
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
也支持 urn:openid:params:grant-type:ciba
授权:
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
然后,可以使用 OidcClient.accessTokens
方法来接受 Map 的额外属性,并传递 auth_req_id
参数以交换令牌授权代码。
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.
Grant scopes
你可能需要请求将特定的一组范围与颁发的访问令牌关联。使用专门的 quarkus.oidc-client.scopes
列表属性,例如: quarkus.oidc-client.scopes=email,phone
You might need to request that a specific set of scopes be associated with an issued access token.
Use a dedicated quarkus.oidc-client.scopes
list property, for example: quarkus.oidc-client.scopes=email,phone
Use OidcClient directly
你可以直接使用 OidcClient
来获取访问令牌,并将它们设置为 HTTP Authorization
头作为 Bearer
方案值。
You can use OidcClient
directly to acquire access tokens and set them in an HTTP Authorization
header as a Bearer
scheme value.
例如,让我们假设 Quarkus 端点必须访问返回用户名微服务。首先,创建一个 REST 客户端:
For example, let’s assume the Quarkus endpoint has to access a microservice that returns a user name. First, create a REST client:
package org.acme.security.openid.connect.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
@RegisterRestClient
@Path("/")
public interface RestClientWithTokenHeaderParam {
@GET
@Produces("text/plain")
@Path("userName")
Uni<String> getUserName(@HeaderParam("Authorization") String authorization);
}
现在,使用 OidcClient
来获取令牌并传播它们:
Now, use OidcClient
to acquire the tokens and propagate them:
package org.acme.security.openid.connect.client;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import io.quarkus.oidc.client.runtime.TokensHelper;
import io.quarkus.oidc.client.OidcClient;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
@Path("/service")
public class OidcClientResource {
@Inject
OidcClient client;
TokensHelper tokenHelper = new TokensHelper(); 1
@Inject
@RestClient
RestClientWithTokenHeaderParam restClient;
@GET
@Path("user-name")
@Produces("text/plain")
public Uni<String> getUserName() {
return tokenHelper.getTokens(client).onItem()
.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
}
}
1 | io.quarkus.oidc.client.runtime.TokensHelper manages the access token acquisition and refresh. |
Inject tokens
你可以注入 Tokens
,其在内部使用 OidcClient
。 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
}
}
Use OidcClients
io.quarkus.oidc.client.OidcClients
是 OidcClient`s - it includes a default `OidcClient
的容器,并且可以这样配置具有名称的客户端:
io.quarkus.oidc.client.OidcClients
is a container of OidcClient`s - it includes a default `OidcClient
and named clients which can be configured like this:
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
在这种情况下,将使用 client-enabled=false
属性禁用默认客户端。 jwt-secret
客户端可以像这样进行访问:
In this case, the default client is disabled with a client-enabled=false
property. The jwt-secret
client can be accessed like this:
import org.eclipse.microprofile.rest.client.inject.RestClient;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClients;
import io.quarkus.oidc.client.runtime.TokensHelper;
@Path("/clients")
public class OidcClientResource {
@Inject
OidcClients clients;
TokensHelper tokenHelper = new TokensHelper();
@Inject
@RestClient
RestClientWithTokenHeaderParam restClient; 1
@GET
@Path("user-name")
@Produces("text/plain")
public Uni<String> getUserName() {
OidcClient client = clients.getClient("jwt-secret");
return tokenHelper.getTokens(client).onItem()
.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
}
}
1 | See the RestClientWithTokenHeaderParam declaration in the Use OidcClient directly section. |
如果同时使用 OIDC multitenancy,每个 OIDC 租户又都有各自的关联 If you also use OIDC multitenancy, and each OIDC tenant has its own associated
|
还可以编程地创建新的 OidcClient
。例如,假设必须在启动时创建它:
You can also create a new OidcClient
programmatically.
For example, let’s assume you must create it at startup time:
package org.acme.security.openid.connect.client;
import java.util.Map;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClientConfig;
import io.quarkus.oidc.client.OidcClientConfig.Grant.Type;
import io.quarkus.oidc.client.OidcClients;
import io.quarkus.runtime.StartupEvent;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
@ApplicationScoped
public class OidcClientCreator {
@Inject
OidcClients oidcClients;
@ConfigProperty(name = "quarkus.oidc.auth-server-url")
String oidcProviderAddress;
private volatile OidcClient oidcClient;
public void startup(@Observes StartupEvent event) {
createOidcClient().subscribe().with(client -> {oidcClient = client;});
}
public OidcClient getOidcClient() {
return oidcClient;
}
private Uni<OidcClient> createOidcClient() {
OidcClientConfig cfg = new OidcClientConfig();
cfg.setId("myclient");
cfg.setAuthServerUrl(oidcProviderAddress);
cfg.setClientId("backend-service");
cfg.getCredentials().setSecret("secret");
cfg.getGrant().setType(Type.PASSWORD);
cfg.setGrantOptions(Map.of("password",
Map.of("username", "alice", "password", "alice")));
return oidcClients.newClient(cfg);
}
}
现在,可以按如下方式使用此客户端:
Now, you can use this client like this:
import org.eclipse.microprofile.rest.client.inject.RestClient;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.client.runtime.TokensHelper;
@Path("/clients")
public class OidcClientResource {
@Inject
OidcClientCreator oidcClientCreator;
TokensHelper tokenHelper = new TokensHelper();
@Inject
@RestClient
RestClientWithTokenHeaderParam restClient; 1
@GET
@Path("user-name")
@Produces("text/plain")
public Uni<String> getUserName() {
return tokenHelper.getTokens(oidcClientCreator.getOidcClient()).onItem()
.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
}
}
1 | See the RestClientWithTokenHeaderParam declaration in the Use OidcClient directly section. |
Inject named OidcClient and tokens
在配置了多个 OidcClient
对象的情况下,可以使用额外的限定符 @NamedOidcClient`而不是 `OidcClients`来指定 `OidcClient
注入目标:
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 org.acme.security.openid.connect.client;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.client.runtime.TokensHelper;
@Path("/clients")
public class OidcClientResource {
@Inject
@NamedOidcClient("jwt-secret")
OidcClient client;
TokensHelper tokenHelper = new TokensHelper();
@Inject
@RestClient
RestClientWithTokenHeaderParam restClient; 1
@GET
@Path("user-name")
@Produces("text/plain")
public Uni<String> getUserName() {
return tokenHelper.getTokens(client).onItem()
.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
}
}
1 | See the RestClientWithTokenHeaderParam declaration in the Use OidcClient directly section. |
可以使用相同的限定符来指定用于 Tokens
注入的 OidcClient
:
The same qualifier can be used to specify the OidcClient
used for a Tokens
injection:
@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());
}
}
Use OidcClient in RestClient Reactive ClientFilter
添加以下 Maven 依赖项:
Add the following Maven Dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-oidc-filter</artifactId>
</dependency>
请注意,它还将引入 io.quarkus:quarkus-oidc-client
。
Note it will also bring io.quarkus:quarkus-oidc-client
.
quarkus-rest-client-oidc-filter
扩展提供了 io.quarkus.oidc.client.filter.OidcClientRequestReactiveFilter
。
quarkus-rest-client-oidc-filter
extension provides io.quarkus.oidc.client.filter.OidcClientRequestReactiveFilter
.
它的工作方式类似于 OidcClientRequestFilter
(请参阅 Use OidcClient in MicroProfile RestClient client filter)- 它使用 OidcClient
获取访问令牌,根据需要刷新令牌并将其设置为 HTTP Authorization
Bearer
架构值。差异在于它使用 Reactive RestClient 并实现了一个非阻塞客户端过滤器,在获取或刷新令牌时不会阻塞当前 IO 线程。
It works similarly to the way OidcClientRequestFilter
does (see resteasy-client-oidc-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.
OidcClientRequestReactiveFilter
会延迟初始令牌获取,直到执行它来避免阻塞 IO 线程。
OidcClientRequestReactiveFilter
delays an initial token acquisition until it is executed to avoid blocking an IO thread.
可以使用 io.quarkus.oidc.client.reactive.filter.OidcClientFilter
或 org.eclipse.microprofile.rest.client.annotation.RegisterProvider
注释有选择地注册 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
默认使用默认 OidcClient
。可以用 quarkus.rest-client-oidc-filter.client-name
配置属性选择已命名的 OidcClient
。还可以通过设置 @OidcClientFilter
注释的 value
属性来选择 OidcClient
。通过注释设置的客户端名称的优先级高于 quarkus.rest-client-oidc-filter.client-name
配置属性。例如,给定 this jwt-secret
命名的 OIDC 客户端声明,可以按如下方式引用此客户端:
OidcClientRequestReactiveFilter
uses a default OidcClient
by default. A named OidcClient
can be selected with a quarkus.rest-client-oidc-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.rest-client-oidc-filter.client-name
configuration property.
For example, given use-oidc-clients 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();
}
Use OidcClient in RestClient ClientFilter
添加以下 Maven 依赖项:
Add the following Maven Dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-client-oidc-filter</artifactId>
</dependency>
请注意,它还将引入 io.quarkus:quarkus-oidc-client
。
Note it will also bring io.quarkus:quarkus-oidc-client
.
quarkus-resteasy-client-oidc-filter
扩展提供了 io.quarkus.oidc.client.filter.OidcClientRequestFilter
Jakarta REST ClientRequestFilter,它使用 OidcClient
获取访问令牌,根据需要刷新令牌并将其设置为 HTTP Authorization
Bearer
架构值。
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.
默认情况下,此过滤器将获得 OidcClient
,以便在初始化时获取第一对访问令牌和刷新令牌。如果访问令牌是短暂的,并且刷新令牌不可用,则应延迟令牌获取,方法是使用 quarkus.oidc-client.early-tokens-acquisition=false
。
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
:
You can selectively register OidcClientRequestFilter
by using either io.quarkus.oidc.client.filter.OidcClientFilter
or org.eclipse.microprofile.rest.client.annotation.RegisterProvider
annotations:
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();
}
或者,如果设置了 quarkus.resteasy-client-oidc-filter.register-filter=true
属性,OidcClientRequestFilter
可以在所有 MP Rest 或 Jakarta REST 客户端中自动注册。
Alternatively, OidcClientRequestFilter
can be registered automatically with all MP Rest or Jakarta REST clients if the quarkus.resteasy-client-oidc-filter.register-filter=true
property is set.
OidcClientRequestFilter
默认使用默认 OidcClient
。可以用 quarkus.resteasy-client-oidc-filter.client-name
配置属性选择已命名的 OidcClient
。还可以通过设置 @OidcClientFilter
注释的 value
属性来选择 OidcClient
。通过注释设置的客户端名称的优先级高于 quarkus.resteasy-client-oidc-filter.client-name
配置属性。例如,给定 this jwt-secret
命名的 OIDC 客户端声明,可以按如下方式引用此客户端:
OidcClientRequestFilter
uses a default OidcClient
by default. A named OidcClient
can be selected with a quarkus.resteasy-client-oidc-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.resteasy-client-oidc-filter.client-name
configuration property.
For example, given use-oidc-clients 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
如果愿意,可以按如下方式使用自己的自定义过滤器和注入 Tokens
:
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`制作者将获取并刷新令牌,而自定义过滤器将决定如何以及何时使用令牌。
The Tokens
producer will acquire and refresh the tokens, and the custom filter will decide how and when to use the token.
您还可以注入已命名的 Tokens
,请参阅 Inject named OidcClient and Tokens
You can also inject named Tokens
, see named-oidc-clients
Refreshing access tokens
OidcClientRequestReactiveFilter
、`OidcClientRequestFilter`和 `Tokens`制作者将刷新当前过期的访问令牌(如果存在刷新令牌)。此外,`quarkus.oidc-client.refresh-token-time-skew`属性可用于先发性访问令牌刷新,以避免发送可能导致 HTTP 401 错误的临近过期的访问令牌。例如,如果此属性设置为 `3S`并且访问令牌将在不到 3 秒内过期,那么此令牌将被自动刷新。
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.
如果需要刷新访问令牌,但没有可用的刷新令牌,则会尝试使用已配置的授权(例如 client_credentials
)来获取新令牌。
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
.
某些 OpenID Connect 提供程序不会在 `client_credentials`授权响应中返回刷新令牌。例如,从 Keycloak 12 开始,默认情况下不会为 `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
如果您的 OpenId Connect 提供程序(例如 Keycloak)支持令牌撤销端点,则可使用 `OidcClient#revokeAccessToken`撤销当前访问令牌。撤销端点 URL 将与令牌请求 URI 一起发现,或可使用 `quarkus.oidc-client.revoke-path`进行配置。
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
.
如果您将访问令牌与 REST 客户端一起使用时失败,并显示 HTTP `401`状态代码或者访问令牌已长时间使用且您希望刷新该令牌,则可能需要撤销该访问令牌。
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`必须向 OpenID Connect Provider 进行身份验证,以便 `client_credentials`和其他授权请求成功。所有 OIDC Client Authentication选项均受支持,例如:
OidcClient
has to authenticate to the OpenID Connect Provider for the client_credentials
and other grant requests to succeed.
All the OIDC Client Authentication options are supported, for example:
client_secret_basic
:
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
或使用从 CredentialsProvider中检索的密钥:
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
# This is the keyring provided to the CredentialsProvider when looking up the secret, set only if required by the CredentialsProvider implementation
quarkus.oidc.credentials.client-secret.provider.keyring-name=oidc
# 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
:
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
:
client_secret_jwt
, signature algorithm is 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
或使用从 CredentialsProvider中检索的密钥,签名算法是 HS256
:
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
# This is the keyring provided to the CredentialsProvider when looking up the secret, set only if required by the CredentialsProvider implementation
quarkus.oidc.credentials.client-secret.provider.keyring-name=oidc
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc-client.credentials.jwt.secret-provider.name=oidc-credentials-provider
private_key_jwt`在 application.properties 中内嵌 PEM 密钥,其中签名算法是 `RS256
:
private_key_jwt
with the PEM key inlined in application.properties, and where the 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=Base64-encoded private key representation
private_key_jwt`使用 PEM 密钥文件,签名算法是 `RS256
:
private_key_jwt
with the PEM key 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-file=privateKey.pem
private_key_jwt`使用 keystore 文件,签名算法是 `RS256
:
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`认证方法可确保没有客户端密钥通过网络传输。
Using client_secret_jwt
or private_key_jwt
authentication methods ensures that no client secret goes over the wire.
Additional JWT authentication options
如果`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
JWT Bearer
RFC7523解释了如何使用 JWT Bearer 令牌对客户端进行身份验证,有关详细信息,请参见 Using JWTs for Client Authentication部分。
RFC7523 explains how JWT Bearer tokens can be used to authenticate clients, see the Using JWTs for Client Authentication section for more information.
可以通过以下方式启用它:
It can be enabled as follows:
quarkus.oidc-client.auth-server-url=${auth-server-url}
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.source=bearer
接下来,必须将 JWT 承载令牌作为 `client_assertion`参数提供给 OIDC 客户端。
Next, the JWT bearer token must be provided as a client_assertion
parameter to the OIDC client.
你可以使用 OidcClient`方法来获取或刷新接受附加授予参数的令牌,例如 `oidcClient.getTokens(Map.of("client_assertion", "ey…"))
。
You can use OidcClient
methods for acquiring or refreshing tokens which accept additional grant parameters, for example, oidcClient.getTokens(Map.of("client_assertion", "ey…"))
.
如果你要使用 OIDC 客户端过滤器,则必须注册一个自定义过滤器,该过滤器将提供此断言。
If you work work with the OIDC client filters then you must register a custom filter which will provide this assertion.
以下是一个 Quarkus REST(以前称为 RESTEasy Reactive)自定义过滤器的示例:
Here is an example of the Quarkus REST (formerly RESTEasy Reactive) custom filter:
package io.quarkus.it.keycloak;
import java.util.Map;
import io.quarkus.oidc.client.reactive.filter.runtime.AbstractOidcClientRequestReactiveFilter;
import io.quarkus.oidc.common.runtime.OidcConstants;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
@Priority(Priorities.AUTHENTICATION)
public class OidcClientRequestCustomFilter extends AbstractOidcClientRequestReactiveFilter {
@Override
protected Map<String, String> additionalParameters() {
return Map.of(OidcConstants.CLIENT_ASSERTION, "ey...");
}
}
以下是一个 RestEasy Classic 自定义过滤器的示例:
Here is an example of the RestEasy Classic custom filter:
package io.quarkus.it.keycloak;
import java.util.Map;
import io.quarkus.oidc.client.filter.runtime.AbstractOidcClientRequestFilter;
import io.quarkus.oidc.common.runtime.OidcConstants;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
@Priority(Priorities.AUTHENTICATION)
public class OidcClientRequestCustomFilter extends AbstractOidcClientRequestFilter {
@Override
protected Map<String, String> additionalParameters() {
return Map.of(OidcConstants.CLIENT_ASSERTION, "ey...");
}
}
Apple POST JWT
Apple OpenID Connect Provider 使用 `client_secret_post`方法,其中密钥是使用 `private_key_jwt`身份验证方法生成的 JWT,但具有 Apple 帐户特定的颁发者和主题属性。
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`支持非标准 `client_secret_post_jwt`身份验证方法,可以按以下方式进行配置:
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}
Mutual TLS
某些 OpenID Connect Providers 要求作为双向 TLS (mTLS
) 身份验证过程的一部分对客户端进行身份验证。
Some OpenID Connect Providers require that a client is authenticated as part of the mutual TLS (mTLS
) authentication process.
可以按以下方式配置 quarkus-oidc-client`来支持 `mTLS
:
quarkus-oidc-client
can be configured as follows to support mTLS
:
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
Testing
首先将以下依赖项添加到测试项目:
Start by adding the following dependencies to your test project:
<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
向测试项目中添加以下依赖项:
Add the following dependencies to your test project:
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
</dependency>
编写一个基于 Wiremock 的 QuarkusTestResourceLifecycleManager
,例如:
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;
}
}
}
准备 REST 测试端点。你可以拥有测试前端端点,它使用注入的 MP REST 客户端和注册的 OidcClient 过滤器来调用下游端点。此端点会将令牌回显回来。例如,请参见 `integration-tests/oidc-client-wiremock`中的 `main`Quarkus 存储库。
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
:
Set application.properties
, for example:
# Use the 'keycloak.url' property set by the test KeycloakRealmResourceManager
quarkus.oidc-client.auth-server-url=${keycloak.url:replaced-by-test-resource}
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
最后,编写测试代码。鉴于上面的基于 Wiremock 的资源,第一个测试调用应返回 `access_token_1`访问令牌,该令牌将在 4 秒后过期。使用 `awaitility`等待大约 5 秒,现在下一个测试调用应返回 `access_token_2`访问令牌,这将确认已刷新过期的 `access_token_1`访问令牌。
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
如果你要使用 Keycloak,可以使用 OpenID Connect Bearer Token Integration testingKeycloak 部分中描述的相同方法。
If you work with Keycloak, you can use the same approach described in the OpenID Connect Bearer Token Integration testing Keycloak section.
How to check the errors in the logs
启用 `io.quarkus.oidc.client.runtime.OidcClientImpl``TRACE`级别的日志记录以查看有关令牌获取和刷新错误的更多详细信息:
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
启用 `io.quarkus.oidc.client.runtime.OidcClientRecorder``TRACE`级别的日志记录以查看有关 OidcClient 初始化错误的更多详细信息:
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
你可以通过注册一个或多个 `OidcRequestFilter`实施来过滤 Quarkus 对 OIDC 提供程序发出的 OIDC 请求,这些实施可以更新或添加新的请求标头。例如,过滤器可以分析请求正文并将摘要作为新标头值添加到其中:
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
quarkus-rest-client-oidc-token-propagation
扩展提供了一个 REST 客户端 io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter
,它简化了身份验证信息的传播。此客户端传播当前活动请求中存在的 bearer token 或从 authorization code flow mechanism 获取的令牌,作为 HTTP Authorization
标头中的 Bearer
方案值。
The quarkus-rest-client-oidc-token-propagation
extension provides a REST 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
注释有选择地注册 AccessTokenRequestReactiveFilter
,例如:
You can selectively register AccessTokenRequestReactiveFilter
by using either io.quarkus.oidc.token.propagation.AccessToken
or org.eclipse.microprofile.rest.client.annotation.RegisterProvider
annotation, for example:
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
可以支持需要在传播代币前交换代币的复杂应用程序。
Additionally, AccessTokenRequestReactiveFilter
can support a complex application that needs to exchange the tokens before propagating them.
如果你使用 Keycloak 或其他支持 Token Exchange 令牌授予的 OIDC 提供商,那么你可以像这样配置 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.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. |
注意 AccessTokenRequestReactiveFilter
将使用 OidcClient
交换当前令牌,而且你可以使用 quarkus.oidc-client.grant-options.exchange
设置你的 OpenID Connect 提供商期望的附加交换属性。
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.
如果你使用 Azure
等提供商,它们 require using JWT bearer token grant 交换当前令牌,那么你可以像这样配置 AccessTokenRequestReactiveFilter
来交换令牌:
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.resteasy-client-oidc-token-propagation.exchange-token=true
AccessTokenRequestReactiveFilter
默认情况下使用默认 OidcClient
。可以使用 quarkus.rest-client-oidc-token-propagation.client-name
配置属性或 io.quarkus.oidc.token.propagation.AccessToken#exchangeTokenClient
注释属性来选择一个命名的 OidcClient
。
AccessTokenRequestReactiveFilter
uses a default OidcClient
by default. A named OidcClient
can be selected with a quarkus.rest-client-oidc-token-propagation.client-name
configuration property or with the io.quarkus.oidc.token.propagation.AccessToken#exchangeTokenClient
annotation attribute.
Token Propagation
quarkus-resteasy-client-oidc-token-propagation
扩展提供了两个 Jakarta REST jakarta.ws.rs.client.ClientRequestFilter
类实现,它们简化了身份验证信息传播。io.quarkus.oidc.token.propagation.AccessTokenRequestFilter
传播当前活动请求中存在的 Bearer token 或从 Authorization code flow mechanism 获取的令牌,作为 HTTP Authorization
标头中的 Bearer
方案值。 io.quarkus.oidc.token.propagation.JsonWebTokenRequestFilter
提供相同的功能,但此外还提供对 JWT 代币的支持。
The quarkus-resteasy-client-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.
当你需要传播当前授权代码流访问令牌时,立即令牌传播将工作得很好——因为代码流访问令牌(与 ID 令牌相反)旨在针对当前 Quarkus 端点传播,以代表当前经过身份验证的用户访问远程服务。
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.
但是,应该避免直接端到端的 Bearer 令牌传播。例如,Client → Service A → Service B
,其中 Service B
接收由 Client
发送给 Service A
的令牌。在这种情况下,Service B
无法区分令牌来自 Service A
还是直接来自 Client
。为了对 Service B
验证令牌来自 Service A
,它应该能够断言新的发布者和受众声明。
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.
此外,复杂应用程序可能需要在传播代币之前交换或更新令牌。例如,当 Service A
访问 Service B
时,访问上下文可能不同。在这种情况下,Service A
可能会得到一个范围较窄或完全不同的范围集来访问 Service B
。
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
如何提供帮助。
The following sections show how AccessTokenRequestFilter
and JsonWebTokenRequestFilter
can help.
RestClient AccessTokenRequestFilter
AccessTokenRequestFilter
将所有令牌视为字符串,因此它可以同时使用 JWT 和不透明令牌。
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
:
You can selectively register AccessTokenRequestFilter
by using either io.quarkus.oidc.token.propagation.AccessToken
or org.eclipse.microprofile.rest.client.annotation.RegisterProvider
, for example:
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();
}
或者,如果 quarkus.resteasy-client-oidc-token-propagation.register-filter
属性设置为 true
并且 quarkus.resteasy-client-oidc-token-propagation.json-web-token
属性设置为 false
(这是默认值),则 AccessTokenRequestFilter
可以在所有 MP Rest 或 Jakarta REST 客户端自动注册。
Alternatively, AccessTokenRequestFilter
can be registered automatically with all MP Rest or Jakarta REST clients if the quarkus.resteasy-client-oidc-token-propagation.register-filter
property is set to true
and quarkus.resteasy-client-oidc-token-propagation.json-web-token
property is set to false
(which is a default value).
Exchange token before propagation
如果当前访问令牌需要在传播之前交换,并且你使用支持 Token Exchange 令牌授予的 Keycloak 或其他 OpenID Connect 提供商,那么你可以像这样配置 AccessTokenRequestFilter
:
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.resteasy-client-oidc-token-propagation.exchange-token=true
如果你使用 Azure
等提供商,它们 require using JWT bearer token grant 交换当前令牌,那么你可以像这样配置 AccessTokenRequestFilter
来交换令牌:
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.resteasy-client-oidc-token-propagation.exchange-token=true
注意 AccessTokenRequestFilter
将使用 OidcClient
交换当前令牌,而且你可以使用 quarkus.oidc-client.grant-options.exchange
设置你的 OpenID Connect 提供商期望的附加交换属性。
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`默认情况下使用默认`OidcClient
。可以使用`quarkus.resteasy-client-oidc-token-propagation.client-name`配置属性选择具有名称的`OidcClient`。
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.
RestClient JsonWebTokenRequestFilter
如果你使用 Bearer JWT 令牌,并且这些令牌可以修改其声明(如`issuer`和`audience`),并再次保护更新后的令牌(例如重新签名),则建议使用`JsonWebTokenRequestFilter`。它需要注入的`org.eclipse.microprofile.jwt.JsonWebToken`,因此无法与不透明的令牌一起使用。此外,如果你的 OpenID Connect 提供商支持令牌交换协议,那么建议改用`AccessTokenRequestFilter` - 因为两者都可以安全地使用`AccessTokenRequestFilter`交换 JWT 和不透明的持有者令牌。
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`使`Service A`实现能够轻松地使用新的`issuer`和`audience`声明值更新注入的`org.eclipse.microprofile.jwt.JsonWebToken
,并使用新签名再次保护更新后的令牌。唯一困难的步骤是确保`Service A`具有签名密钥;它应从安全文件系统或 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; 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
,例如:
You can selectively register JsonWebTokenRequestFilter
by using either io.quarkus.oidc.token.propagation.JsonWebToken
or org.eclipse.microprofile.rest.client.annotation.RegisterProvider
, for example:
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.resteasy-client-oidc-token-propagation.register-filter`和 `quarkus.resteasy-client-oidc-token-propagation.json-web-token`属性都设置为 `true
,则可以自动使用所有 MicroProfile REST 或 Jakarta REST 客户端注册 JsonWebTokenRequestFilter
。
Alternatively, JsonWebTokenRequestFilter
can be registered automatically with all MicroProfile REST or Jakarta REST clients if both quarkus.resteasy-client-oidc-token-propagation.register-filter
and quarkus.resteasy-client-oidc-token-propagation.json-web-token
properties are set to true
.
Update token before propagation
如果注入令牌需要使用新签名更新并再次保护其`iss`(颁发者)或`aud`(受众)声明,则可以像这样配置`JsonWebTokenRequestFilter`:
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.resteasy-client-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
如前所述,如果你使用 Keycloak 或支持令牌交换协议的 OpenID Connect 提供商,则使用`AccessTokenRequestFilter`。
As mentioned, use AccessTokenRequestFilter
if you work with Keycloak or an OpenID Connect Provider that supports a Token Exchange protocol.
Testing
你可以按照 OpenID Connect Bearer Token Integration testing部分中的说明生成令牌。准备 REST 测试端点。你可以使用注册的令牌传播过滤器注入 MP REST 客户端的测试前端端点,调用下游端点。例如,请查看 main`Quarkus 存储库中的 `integration-tests/resteasy-client-oidc-token-propagation
。
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/resteasy-client-oidc-token-propagation
in the main
Quarkus repository.
Token Propagation Reactive
添加以下 Maven 依赖项:
Add the following Maven Dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-resteasy-client-oidc-token-propagation</artifactId>
</dependency>
quarkus-rest-client-resteasy-client-oidc-token-propagation`扩展提供 `io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter
,可用于传播当前`Bearer`或 `Authorization Code Flow`访问令牌。
The quarkus-rest-client-resteasy-client-oidc-token-propagation
extension provides io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter
which can be used to propagate the current Bearer
or Authorization Code Flow
access tokens.
`quarkus-rest-client-resteasy-client-oidc-token-propagation`扩展(与非反应式 `quarkus-resteasy-client-oidc-token-propagation`扩展相对)当前不支持在传播之前交换或重新签名令牌。但是,这些功能可能会在将来添加。
The quarkus-rest-client-resteasy-client-oidc-token-propagation
extension (as opposed to the non-reactive quarkus-resteasy-client-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
`quarkus-oidc-client-graphql`扩展提供了一种将 OIDC 客户端集成到 GraphQL clients的方法,这与 REST 客户端使用的方法类似。当此扩展处于活动状态时,通过属性(而不是通过构建器以编程方式)配置的任何 GraphQL 客户端都将使用 OIDC 客户端获取访问令牌,然后将其设置为`Authorization`标头值。OIDC 客户端还将刷新过期的访问令牌。
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.
要配置由 GraphQL 客户端使用哪个 OIDC 客户端,请使用 `quarkus.oidc-client-graphql.client-name`属性选择一个已配置的 OIDC 客户端,例如:
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
如果你未指定 `quarkus.oidc-client-graphql.client-name`属性,GraphQL 客户端将使用默认 OIDC 客户端(无显式名称)。 |
If you don’t specify the |
具体针对类型安全 GraphQL 客户端,你可以通过使用 `@io.quarkus.oidc.client.filter.OidcClientFilter`注释 `GraphQLClientApi`接口在逐个客户端的基础上覆盖此设置。例如:
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.
}
为了能够使用编程方式创建的 GraphQL 客户端,这两个构建器(VertxDynamicGraphQLClientBuilder`和 `VertxTypesafeGraphQLClientBuilder
)都包含一个方法 dynamicHeader(String,
Uni<String>
),允许你插入可能针对每个请求而更改的标头。若要插入 OIDC 客户端,请使用
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();