OpenID Connect (OIDC) and OAuth2 client and filters

您可以使用 Quarkus 扩展来进行 OpenID Connect 和 OAuth 2.0 访问令牌管理,重点在于获取、刷新和传播令牌。 这包括以下内容:

  • 使用 quarkus-oidc-clientquarkus-rest-client-oidc-filterquarkus-resteasy-client-oidc-filter 扩展从 OpenID Connect 和 OAuth 2.0 兼容授权服务器(例如 Keycloak)获取和刷新访问令牌。

  • 使用 quarkus-rest-client-oidc-token-propagationquarkus-resteasy-client-oidc-token-propagation 扩展来传播当前 BearerAuthorization Code Flow 访问令牌。

这些扩展管理的访问令牌可以用作 HTTP Authorization Bearer 令牌来访问远程服务。 另请参见 OpenID Connect client and token propagation quickstart

OidcClient

添加以下依赖项:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc-client</artifactId>
</dependency>

quarkus-oidc-client 扩展提供了一个反应式 io.quarkus.oidc.client.OidcClient,该反应式可用于通过 SmallRye Mutiny UniVert.x WebClient 来获取和刷新令牌。

OidcClient 在构建时通过 IDP 令牌端点 URL 初始化,该 URL 可以自动发现或手动配置。OidcClient 使用此端点通过令牌授权(如 client_credentialspassword)获取访问令牌,并通过 refresh_token 授权刷新令牌。

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

或者,如果发现端点不可用,或者您想节省发现端点往返时间,则可以禁用发现并使用相对路径值配置令牌端点地址。例如:

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

在这种情况下,无需设置 quarkus.oidc-client.auth-server-urlquarkus.oidc-client.discovery-enabled

Supported token grants

OidcClient 用于获取令牌的主要令牌授权是 client_credentials(默认)和 password 授权。

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

client_credentials 授权允许通过使用 quarkus.oidc-client.grant-options.client.<param-name>=<value> 来设置令牌请求的额外参数。以下是使用 audience 参数设置预期令牌接收者的方法:

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

它可以通过使用 quarkus.oidc-client.grant-options.password 配置前缀进行进一步自定义,类似于可以自定义客户端凭证授权的方式。

Other grants

OidcClient 还可以帮助通过使用需要一些配置中无法捕获的额外输入参数的授权来获取令牌。这些授权为 refresh_token (带外部刷新令牌)、 authorization_code`以及可用于交换当前访问令牌的两个授权,即 `urn:ietf:params:oauth:grant-type:token-exchangeurn:ietf:params:oauth:grant-type:jwt-bearer

如果你需要获取访问令牌并将现有刷新令牌发布到当前 Quarkus 端点,你必须使用 refresh_token 授权。此授权使用带外刷新令牌来获取新的令牌集。在这种情况下,请按如下方式配置 OidcClient

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 方法与提供的刷新令牌一起使用以获取访问令牌。

如果你正在构建一个复杂的微服务应用程序并在多个服务中使用同一个 Bearer`令牌,则可能需要使用 `urn:ietf:params:oauth:grant-type:token-exchangeurn:ietf:params:oauth:grant-type:jwt-bearer 授权。有关更多详细信息,请参阅 Token Propagation in MicroProfile RestClient Reactive filterToken Propagation in MicroProfile RestClient filter

如果你由于某种原因无法使用 Quarkus OIDC extension 来支持授权代码流程,则可能需要使用 OidcClient 来支持 authorization code 授权。如果你有非常充分的理由来实现授权代码流程,则可以按如下方式配置 OidcClient

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,并传递当前 coderedirect_uri 参数以交换授权代码以获取令牌。

OidcClient 也支持 urn:openid:params:grant-type:ciba 授权:

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 参数以交换令牌授权代码。

Grant scopes

你可能需要请求将特定的一组范围与颁发的访问令牌关联。使用专门的 quarkus.oidc-client.scopes 列表属性,例如: quarkus.oidc-client.scopes=email,phone

Use OidcClient directly

你可以直接使用 OidcClient 来获取访问令牌,并将它们设置为 HTTP Authorization 头作为 Bearer 方案值。

例如,让我们假设 Quarkus 端点必须访问返回用户名微服务。首先,创建一个 REST 客户端:

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 来获取令牌并传播它们:

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 管理访问令牌的获取和刷新。

Inject tokens

你可以注入 Tokens,其在内部使用 OidcClientTokens 可用于获取访问令牌并在必要时刷新它们:

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.OidcClientsOidcClient`s - it includes a default `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

在这种情况下,将使用 client-enabled=false 属性禁用默认客户端。 jwt-secret 客户端可以像这样进行访问:

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 请参阅 Use OidcClient directly 部分中的 RestClientWithTokenHeaderParam 声明。

如果同时使用 OIDC multitenancy,每个 OIDC 租户又都有各自的关联 OidcClient,可以使用 Vert.x RoutingContext tenant-id 属性。例如:

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.vertx.ext.web.RoutingContext;

@Path("/clients")
public class OidcClientResource {

    @Inject
    OidcClients clients;
    @Inject
    RoutingContext context;

    @GET
    public String getResponse() {
        String tenantId = context.get("tenant-id");
        // named OIDC tenant and client configurations use the same key:
        OidcClient client = clients.getClient(tenantId);
        //Use this client to get the token
    }
}

还可以编程地创建新的 OidcClient。例如,假设必须在启动时创建它:

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);
    }
}

现在,可以按如下方式使用此客户端:

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 请参阅 Use OidcClient directly 部分中的 RestClientWithTokenHeaderParam 声明。

Inject named OidcClient and tokens

在配置了多个 OidcClient 对象的情况下,可以使用额外的限定符 @NamedOidcClient`而不是 `OidcClients`来指定 `OidcClient 注入目标:

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 请参阅 Use OidcClient directly 部分中的 RestClientWithTokenHeaderParam 声明。

可以使用相同的限定符来指定用于 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());
    }
}

Use OidcClient in RestClient Reactive ClientFilter

添加以下 Maven 依赖项:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client-oidc-filter</artifactId>
</dependency>

请注意,它还将引入 io.quarkus:quarkus-oidc-client

quarkus-rest-client-oidc-filter 扩展提供了 io.quarkus.oidc.client.filter.OidcClientRequestReactiveFilter

它的工作方式类似于 OidcClientRequestFilter(请参阅 Use OidcClient in MicroProfile RestClient client filter)- 它使用 OidcClient 获取访问令牌,根据需要刷新令牌并将其设置为 HTTP Authorization Bearer 架构值。差异在于它使用 Reactive RestClient 并实现了一个非阻塞客户端过滤器,在获取或刷新令牌时不会阻塞当前 IO 线程。

OidcClientRequestReactiveFilter 会延迟初始令牌获取,直到执行它来避免阻塞 IO 线程。

可以使用 io.quarkus.oidc.client.reactive.filter.OidcClientFilterorg.eclipse.microprofile.rest.client.annotation.RegisterProvider 注释有选择地注册 OidcClientRequestReactiveFilter

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

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 客户端声明,可以按如下方式引用此客户端:

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 依赖项:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-client-oidc-filter</artifactId>
</dependency>

请注意,它还将引入 io.quarkus:quarkus-oidc-client

quarkus-resteasy-client-oidc-filter 扩展提供了 io.quarkus.oidc.client.filter.OidcClientRequestFilter Jakarta REST ClientRequestFilter,它使用 OidcClient 获取访问令牌,根据需要刷新令牌并将其设置为 HTTP Authorization Bearer 架构值。

默认情况下,此过滤器将获得 OidcClient,以便在初始化时获取第一对访问令牌和刷新令牌。如果访问令牌是短暂的,并且刷新令牌不可用,则应延迟令牌获取,方法是使用 quarkus.oidc-client.early-tokens-acquisition=false

可以使用 io.quarkus.oidc.client.filter.OidcClientFilterorg.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();
}

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 客户端中自动注册。

OidcClientRequestFilter 默认使用默认 OidcClient。可以用 quarkus.resteasy-client-oidc-filter.client-name 配置属性选择已命名的 OidcClient。还可以通过设置 @OidcClientFilter 注释的 value 属性来选择 OidcClient。通过注释设置的客户端名称的优先级高于 quarkus.resteasy-client-oidc-filter.client-name 配置属性。例如,给定 this jwt-secret 命名的 OIDC 客户端声明,可以按如下方式引用此客户端:

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

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`和 `Tokens`制作者将刷新当前过期的访问令牌(如果存在刷新令牌)。此外,`quarkus.oidc-client.refresh-token-time-skew`属性可用于先发性访问令牌刷新,以避免发送可能导致 HTTP 401 错误的临近过期的访问令牌。例如,如果此属性设置为 `3S`并且访问令牌将在不到 3 秒内过期,那么此令牌将被自动刷新。

如果需要刷新访问令牌,但没有可用的刷新令牌,则会尝试使用已配置的授权(例如 client_credentials)来获取新令牌。

某些 OpenID Connect 提供程序不会在 `client_credentials`授权响应中返回刷新令牌。例如,从 Keycloak 12 开始,默认情况下不会为 `client_credentials`返回刷新令牌。这些提供程序还可能限制使用刷新令牌的次数。

Revoking access tokens

如果您的 OpenId Connect 提供程序(例如 Keycloak)支持令牌撤销端点,则可使用 `OidcClient#revokeAccessToken`撤销当前访问令牌。撤销端点 URL 将与令牌请求 URI 一起发现,或可使用 `quarkus.oidc-client.revoke-path`进行配置。

如果您将访问令牌与 REST 客户端一起使用时失败,并显示 HTTP `401`状态代码或者访问令牌已长时间使用且您希望刷新该令牌,则可能需要撤销该访问令牌。

可以通过使用刷新令牌请求令牌刷新来实现这一点。但是,如果刷新令牌不可用,您可以先撤销它然后再请求新访问令牌来刷新它。

OidcClient authentication

`OidcClient`必须向 OpenID Connect Provider 进行身份验证,以便 `client_credentials`和其他授权请求成功。所有 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

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中检索的密钥:

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

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

或使用从 CredentialsProvider中检索的密钥,签名算法是 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

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

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

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`认证方法可确保没有客户端密钥通过网络传输。

Additional JWT authentication options

如果`client_secret_jwt`或`private_key_jwt`身份验证方法中任一方法被使用,则 JWT 签名算法、密钥标识符、目标受众、主体和颁发者可以自定义,例如:

# 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部分。

可以通过以下方式启用它:

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 客户端。

你可以使用 OidcClient`方法来获取或刷新接受附加授予参数的令牌,例如 `oidcClient.getTokens(Map.of("client_assertion", "ey…​"))

如果你要使用 OIDC 客户端过滤器,则必须注册一个自定义过滤器,该过滤器将提供此断言。

以下是一个 Quarkus REST(以前称为 RESTEasy Reactive)自定义过滤器的示例:

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 自定义过滤器的示例:

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 帐户特定的颁发者和主题属性。

`quarkus-oidc-client`支持非标准 `client_secret_post_jwt`身份验证方法,可以按以下方式进行配置:

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) 身份验证过程的一部分对客户端进行身份验证。

可以按以下方式配置 quarkus-oidc-client`来支持 `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

首先将以下依赖项添加到测试项目:

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

编写一个基于 Wiremock 的 QuarkusTestResourceLifecycleManager,例如:

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 存储库。

例如,设置 application.properties

# 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`访问令牌。

Keycloak

如果你要使用 Keycloak,可以使用 OpenID Connect Bearer Token Integration testingKeycloak 部分中描述的相同方法。

How to check the errors in the logs

启用 `io.quarkus.oidc.client.runtime.OidcClientImpl``TRACE`级别的日志记录以查看有关令牌获取和刷新错误的更多详细信息:

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 初始化错误的更多详细信息:

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 请求,这些实施可以更新或添加新的请求标头。例如,过滤器可以分析请求正文并将摘要作为新标头值添加到其中:

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 方案值。

你可以使用 io.quarkus.oidc.token.propagation.AccessTokenorg.eclipse.microprofile.rest.client.annotation.RegisterProvider 注释有选择地注册 AccessTokenRequestReactiveFilter,例如:

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.AccessToken;

@RegisterRestClient
@AccessToken
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

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 可以支持需要在传播代币前交换代币的复杂应用程序。

如果你使用 Keycloak 或其他支持 Token Exchange 令牌授予的 OIDC 提供商,那么你可以像这样配置 AccessTokenRequestReactiveFilter 来交换令牌:

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 请注意,当使用 io.quarkus.oidc.token.propagation.AccessToken#exchangeTokenClient 注释属性设置 OidcClient 名称时,将忽略 exchange-token 配置属性。

注意 AccessTokenRequestReactiveFilter 将使用 OidcClient 交换当前令牌,而且你可以使用 quarkus.oidc-client.grant-options.exchange 设置你的 OpenID Connect 提供商期望的附加交换属性。

如果你使用 Azure 等提供商,它们 require using JWT bearer token grant 交换当前令牌,那么你可以像这样配置 AccessTokenRequestReactiveFilter 来交换令牌:

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

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 代币的支持。

当你需要传播当前授权代码流访问令牌时,立即令牌传播将工作得很好——因为代码流访问令牌(与 ID 令牌相反)旨在针对当前 Quarkus 端点传播,以代表当前经过身份验证的用户访问远程服务。

但是,应该避免直接端到端的 Bearer 令牌传播。例如,Client → Service A → Service B,其中 Service B 接收由 Client 发送给 Service A 的令牌。在这种情况下,Service B 无法区分令牌来自 Service A 还是直接来自 Client。为了对 Service B 验证令牌来自 Service A,它应该能够断言新的发布者和受众声明。

此外,复杂应用程序可能需要在传播代币之前交换或更新令牌。例如,当 Service A 访问 Service B 时,访问上下文可能不同。在这种情况下,Service A 可能会得到一个范围较窄或完全不同的范围集来访问 Service B

以下部分展示了 AccessTokenRequestFilterJsonWebTokenRequestFilter 如何提供帮助。

RestClient AccessTokenRequestFilter

AccessTokenRequestFilter 将所有令牌视为字符串,因此它可以同时使用 JWT 和不透明令牌。

你可以使用 io.quarkus.oidc.token.propagation.AccessTokenorg.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();
}

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 客户端自动注册。

Exchange token before propagation

如果当前访问令牌需要在传播之前交换,并且你使用支持 Token Exchange 令牌授予的 Keycloak 或其他 OpenID Connect 提供商,那么你可以像这样配置 AccessTokenRequestFilter

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 来交换令牌:

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 提供商期望的附加交换属性。

AccessTokenRequestFilter`默认情况下使用默认`OidcClient。可以使用`quarkus.resteasy-client-oidc-token-propagation.client-name`配置属性选择具有名称的`OidcClient`。

RestClient JsonWebTokenRequestFilter

如果你使用 Bearer JWT 令牌,并且这些令牌可以修改其声明(如`issuer`和`audience`),并再次保护更新后的令牌(例如重新签名),则建议使用`JsonWebTokenRequestFilter`。它需要注入的`org.eclipse.microprofile.jwt.JsonWebToken`,因此无法与不透明的令牌一起使用。此外,如果你的 OpenID Connect 提供商支持令牌交换协议,那么建议改用`AccessTokenRequestFilter` - 因为两者都可以安全地使用`AccessTokenRequestFilter`交换 JWT 和不透明的持有者令牌。

JsonWebTokenRequestFilter`使`Service A`实现能够轻松地使用新的`issuer`和`audience`声明值更新注入的`org.eclipse.microprofile.jwt.JsonWebToken,并使用新签名再次保护更新后的令牌。唯一困难的步骤是确保`Service A`具有签名密钥;它应从安全文件系统或 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();
}

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

Update token before propagation

如果注入令牌需要使用新签名更新并再次保护其`iss`(颁发者)或`aud`(受众)声明,则可以像这样配置`JsonWebTokenRequestFilter`:

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

Testing

你可以按照 OpenID Connect Bearer Token Integration testing部分中的说明生成令牌。准备 REST 测试端点。你可以使用注册的令牌传播过滤器注入 MP REST 客户端的测试前端端点,调用下游端点。例如,请查看 main`Quarkus 存储库中的 `integration-tests/resteasy-client-oidc-token-propagation

Token Propagation Reactive

添加以下 Maven 依赖项:

<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`访问令牌。

`quarkus-rest-client-resteasy-client-oidc-token-propagation`扩展(与非反应式 `quarkus-resteasy-client-oidc-token-propagation`扩展相对)当前不支持在传播之前交换或重新签名令牌。但是,这些功能可能会在将来添加。

GraphQL client integration

`quarkus-oidc-client-graphql`扩展提供了一种将 OIDC 客户端集成到 GraphQL clients的方法,这与 REST 客户端使用的方法类似。当此扩展处于活动状态时,通过属性(而不是通过构建器以编程方式)配置的任何 GraphQL 客户端都将使用 OIDC 客户端获取访问令牌,然后将其设置为`Authorization`标头值。OIDC 客户端还将刷新过期的访问令牌。

要配置由 GraphQL 客户端使用哪个 OIDC 客户端,请使用 `quarkus.oidc-client-graphql.client-name`属性选择一个已配置的 OIDC 客户端,例如:

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 客户端(无显式名称)。

具体针对类型安全 GraphQL 客户端,你可以通过使用 `@io.quarkus.oidc.client.filter.OidcClientFilter`注释 `GraphQLClientApi`接口在逐个客户端的基础上覆盖此设置。例如:

@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 客户端,请使用

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

Configuration reference

OIDC client

Unresolved include directive in modules/ROOT/pages/security-openid-connect-client-reference.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-oidc-client.adoc[]

OIDC token propagation

Unresolved include directive in modules/ROOT/pages/security-openid-connect-client-reference.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-rest-client-oidc-token-propagation.adoc[]