OpenID Connect client and token propagation quickstart

了解如何在应用程序中使用 OpenID Connect (OIDC) 和 OAuth2 客户端以及过滤器来获取、刷新和传播访问令牌。 有关 Quarkus 中 OIDC ClientToken Propagation 支持的更多信息,请参阅 OpenID Connect (OIDC) and OAuth2 client and filters reference guide。 若要使用带有人员令牌授权来保护应用程序,请参阅 OpenID Connect (OIDC) Bearer token authentication 指南。

Prerequisites

如要完成本指南,您需要:

  • Roughly 15 minutes

  • An IDE

  • 安装了 JDK 17+,已正确配置 JAVA_HOME

  • Apache Maven ${proposed-maven-version}

  • 如果你想使用 Quarkus CLI, 则可以选择使用

  • 如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately

Architecture

在此示例中,应用程序是使用两个 Jakarta REST 资源,FrontendResourceProtectedResource`构建的。此处,`FrontendResource 使用以下三种方法之一将访问令牌传播至 ProtectedResource

  • 它可以通过使用 OIDC 客户端过滤器在传播之前获取令牌。

  • 它可以通过使用以编程方式创建的 OIDC 客户端获取令牌,并通过将其作为 HTTP Authorization 标头值传递给 REST 客户端方法来传播令牌。

  • 它可以使用 OIDC 令牌传播过滤器来传播传入的访问令牌。

FrontendResource 有 8 个端点:

  • /frontend/user-name-with-oidc-client-token

  • /frontend/admin-name-with-oidc-client-token

  • /frontend/user-name-with-oidc-client-token-header-param

  • /frontend/admin-name-with-oidc-client-token-header-param

  • /frontend/user-name-with-oidc-client-token-header-param-blocking

  • /frontend/admin-name-with-oidc-client-token-header-param-blocking

  • /frontend/user-name-with-propagated-token

  • /frontend/admin-name-with-propagated-token

当调用 /frontend/user-name-with-oidc-client-token/frontend/admin-name-with-oidc-client-token 端点时,FrontendResource 使用具有 OIDC 客户端过滤器的 REST 客户端获取访问令牌并将其传播到 ProtectedResource。当调用 /frontend/user-name-with-oidc-client-token-header-param/frontend/admin-name-with-oidc-client-token-header-param 端点时,FrontendResource 使用 以编程方式创建的 OIDC 客户端获取访问令牌并将其传播到 ProtectedResource,方法是将其作为 HTTP Authorization 标头值传递给 REST 客户端方法。当调用 /frontend/user-name-with-propagated-token/frontend/admin-name-with-propagated-token 端点时,FrontendResource 使用带有 OIDC Token Propagation Filter 的 REST 客户端将当前传入的访问令牌传播到 ProtectedResource

ProtectedResource 有两个端点:

  • /protected/user-name

  • /protected/admin-name

两个端点返回从传入访问令牌中提取的用户名,该用户名已从 ProtectedResource 传播到 FrontendResource。这些端点之间的唯一区别在于,仅当当前访问令牌具有 user 角色时才允许调用 /protected/user-name,并且仅当当前访问令牌具有 admin 角色时才允许调用 /protected/admin-name

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

克隆 Git 存储库: git clone $${quickstarts-base-url}.git,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。

此解决方案位于 security-openid-connect-client-quickstart directory

Creating the Maven project

首先,你需要一个新项目。使用以下命令创建一个新项目:

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

适用于 Windows 用户:

  • 如果使用 cmd,(不要使用反斜杠 \ ,并将所有内容放在同一行上)

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

它会生成一个 Maven 项目,导入 oidcrest-client-oidc-filterrest-client-oidc-token-propagationrest 扩展。

如果您已经配置了 Quarkus 项目,可以通过在项目根目录中运行以下命令将这些扩展添加到您的项目:

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'

它会将以下扩展添加到您的构建文件中:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client-oidc-filter</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client-oidc-token-propagation</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-oidc,rest-client-oidc-filter,rest-client-oidc-token-propagation,rest")

Writing the application

首先实现 ProtectedResource

package org.acme.security.openid.connect.client;

import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;

import org.eclipse.microprofile.jwt.JsonWebToken;

@Path("/protected")
@Authenticated
public class ProtectedResource {

    @Inject
    JsonWebToken principal;

    @GET
    @RolesAllowed("user")
    @Produces("text/plain")
    @Path("userName")
    public Uni<String> userName() {
        return Uni.createFrom().item(principal.getName());
    }

    @GET
    @RolesAllowed("admin")
    @Produces("text/plain")
    @Path("adminName")
    public Uni<String> adminName() {
        return Uni.createFrom().item(principal.getName());
    }
}

ProtectedResourceuserName()adminName() 方法返回用户名。用户名从当前 JsonWebToken 中提取。

接下来,添加以下 REST 客户端:

  1. RestClientWithOidcClientFilter,它使用 quarkus-rest-client-oidc-filter 扩展提供的 OIDC 客户端过滤器来获取和传播访问令牌。

  2. RestClientWithTokenHeaderParam,它接受由以编程方式创建的 OidcClient 获取的令牌作为 HTTP Authorization 标头值。

  3. RestClientWithTokenPropagationFilter,它使用 quarkus-rest-client-oidc-token-propagation 扩展提供的 OIDC 令牌传播过滤器来获取和传播访问令牌。

添加 RestClientWithOidcClientFilter REST 客户端:

package org.acme.security.openid.connect.client;

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

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import io.quarkus.oidc.client.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;

@RegisterRestClient
@OidcClientFilter 1
@Path("/")
public interface RestClientWithOidcClientFilter {

    @GET
    @Produces("text/plain")
    @Path("userName")
    Uni<String> getUserName();

    @GET
    @Produces("text/plain")
    @Path("adminName")
    Uni<String> getAdminName();
}
1 使用 REST 客户端注册 OIDC 客户端过滤器以获取和传播令牌。

添加 RestClientWithTokenHeaderParam 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); 1

    @GET
    @Produces("text/plain")
    @Path("adminName")
    Uni<String> getAdminName(@HeaderParam("Authorization") String authorization); 1
}
1 RestClientWithTokenHeaderParam REST 客户端预期令牌将作为 HTTP Authorization 标头值传递给它。

添加 RestClientWithTokenPropagationFilter REST 客户端:

package org.acme.security.openid.connect.client;

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

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import io.quarkus.oidc.token.propagation.AccessToken;

import io.smallrye.mutiny.Uni;

@RegisterRestClient
@AccessToken 1
@Path("/")
public interface RestClientWithTokenPropagationFilter {

    @GET
    @Produces("text/plain")
    @Path("userName")
    Uni<String> getUserName();

    @GET
    @Produces("text/plain")
    @Path("adminName")
    Uni<String> getAdminName();
}
1 使用一个 REST 客户端注册 OIDC 令牌传播过滤器,以传播传入的已有令牌。

不要在同一 REST 客户端中使用 RestClientWithOidcClientFilterRestClientWithTokenPropagationFilter 接口,因为它们可能会冲突,从而导致问题。例如,OIDC 客户端过滤器可以覆盖来自 OIDC 令牌传播过滤器的令牌,或者传播过滤器在尝试传播令牌而令牌不可用时可能无法正确工作,而是希望 OIDC 客户端过滤器获取新的令牌。

另外,添加 OidcClientCreator 以在启动时以编程方式创建一个 OIDC 客户端。OidcClientCreator 支持 RestClientWithTokenHeaderParam REST 客户端调用:

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; 1
    @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);
    }
}
1 OidcClients 可用于检索已初始化的、命名的 OIDC 客户端以及按需创建新的 OIDC 客户端。

现在,通过添加 FrontendResource 来完成应用程序的创建:

package org.acme.security.openid.connect.client;

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

import io.quarkus.oidc.client.Tokens;
import io.quarkus.oidc.client.runtime.TokensHelper;

import org.eclipse.microprofile.rest.client.inject.RestClient;

import io.smallrye.mutiny.Uni;

@Path("/frontend")
public class FrontendResource {
    @Inject
    @RestClient
    RestClientWithOidcClientFilter restClientWithOidcClientFilter; 1

    @Inject
    @RestClient
    RestClientWithTokenPropagationFilter restClientWithTokenPropagationFilter; 2

    @Inject
    OidcClientCreator oidcClientCreator;
    TokensHelper tokenHelper = new TokensHelper(); 5
    @Inject
    @RestClient
    RestClientWithTokenHeaderParam restClientWithTokenHeaderParam; 3

    @GET
    @Path("user-name-with-oidc-client-token")
    @Produces("text/plain")
    public Uni<String> getUserNameWithOidcClientToken() { 1
        return restClientWithOidcClientFilter.getUserName();
    }

    @GET
    @Path("admin-name-with-oidc-client-token")
    @Produces("text/plain")
    public Uni<String> getAdminNameWithOidcClientToken() { 1
	return restClientWithOidcClientFilter.getAdminName();
    }

    @GET
    @Path("user-name-with-propagated-token")
    @Produces("text/plain")
    public Uni<String> getUserNameWithPropagatedToken() { 2
        return restClientWithTokenPropagationFilter.getUserName();
    }

    @GET
    @Path("admin-name-with-propagated-token")
    @Produces("text/plain")
    public Uni<String> getAdminNameWithPropagatedToken() { 2
        return restClientWithTokenPropagationFilter.getAdminName();
    }

    @GET
    @Path("user-name-with-oidc-client-token-header-param")
    @Produces("text/plain")
    public Uni<String> getUserNameWithOidcClientTokenHeaderParam() { 3
    	return tokenHelper.getTokens(oidcClientCreator.getOidcClient()).onItem()
        		.transformToUni(tokens -> restClientWithTokenHeaderParam.getUserName("Bearer " + tokens.getAccessToken()));
    }

    @GET
    @Path("admin-name-with-oidc-client-token-header-param")
    @Produces("text/plain")
    public Uni<String> getAdminNameWithOidcClientTokenHeaderParam() { 3
    	return tokenHelper.getTokens(oidcClientCreator.getOidcClient()).onItem()
        		.transformToUni(tokens -> restClientWithTokenHeaderParam.getAdminName("Bearer " + tokens.getAccessToken()));
    }

    @GET
    @Path("user-name-with-oidc-client-token-header-param-blocking")
    @Produces("text/plain")
    public String getUserNameWithOidcClientTokenHeaderParamBlocking() { 4
    	Tokens tokens = tokenHelper.getTokens(oidcClientCreator.getOidcClient()).await().indefinitely();
        return restClientWithTokenHeaderParam.getUserName("Bearer " + tokens.getAccessToken()).await().indefinitely();
    }

    @GET
    @Path("admin-name-with-oidc-client-token-header-param-blocking")
    @Produces("text/plain")
    public String getAdminNameWithOidcClientTokenHeaderParamBlocking() { 4
    	Tokens tokens = tokenHelper.getTokens(oidcClientCreator.getOidcClient()).await().indefinitely();
        return restClientWithTokenHeaderParam.getAdminName("Bearer " + tokens.getAccessToken()).await().indefinitely();
    }

}
1 FrontendResource 使用注入的 RestClientWithOidcClientFilter REST 客户端和 OIDC 客户端过滤器,以在调用 /frontend/user-name-with-oidc-client-token/frontend/admin-name-with-oidc-client-token 时,获取访问令牌并将其传播到 ProtectedResource
2 FrontendResource 使用注入的 RestClientWithTokenPropagationFilter REST 客户端和 OIDC 令牌传播过滤器,以在调用 /frontend/user-name-with-propagated-token/frontend/admin-name-with-propagated-token 时,将当前传入的访问令牌传播到 ProtectedResource
3 FrontendResource 使用以编程方式创建的 OIDC 客户端,以在调用 /frontend/user-name-with-oidc-client-token-header-param/frontend/admin-name-with-oidc-client-token-header-param 时,获取访问令牌并将其传播到 ProtectedResource,这是通过直接将其作为 HTTP Authorization 标头值传递到已注入的 RestClientWithTokenHeaderParam REST 客户端方法来实现的。
4 有时可能需要以阻塞方式获取令牌,然后再使用 REST 客户端传播令牌。本示例展示了如何在这种情况中获取令牌。
5 当直接使用 OIDC 客户端而没有 OIDC 客户端过滤器时,io.quarkus.oidc.client.runtime.TokensHelper 会成为一个有用的工具。若要使用 TokensHelper,请将 OIDC 客户端传递给它以获取令牌,并且 TokensHelper 将以线程安全的方式获取令牌并在必要时刷新令牌。

最后,添加一个 Jakarta REST ExceptionMapper

package org.acme.security.openid.connect.client;

import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.ClientWebApplicationException;

@Provider
public class FrontendExceptionMapper implements ExceptionMapper<ClientWebApplicationException> {

	@Override
	public Response toResponse(ClientWebApplicationException t) {
		return Response.status(t.getResponse().getStatus()).build();
	}

}

仅添加此异常映射程序以在测试期间验证当令牌没有预期角色时,ProtectedResource 是否返回 403。如果没有此映射程序,Quarkus REST (formerly RESTEasy Reactive) 将正确地将从 REST 客户端调用逃逸的异常转换为 500,以避免泄露诸如 ProtectedResource 等下游资源的信息。然而,在测试中,将不可能断言 500 是由授权异常而不是某些内部错误引起的。

Configuring the application

在准备了代码后,请配置应用程序:

# Configure OIDC

%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=backend-service
quarkus.oidc.credentials.secret=secret

# Tell Dev Services for Keycloak to import the realm file
# This property is ineffective when running the application in JVM or Native modes but only in dev and test modes.

quarkus.keycloak.devservices.realm-path=quarkus-realm.json

# Configure OIDC Client

quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url}
quarkus.oidc-client.client-id=${quarkus.oidc.client-id}
quarkus.oidc-client.credentials.secret=${quarkus.oidc.credentials.secret}
quarkus.oidc-client.grant.type=password
quarkus.oidc-client.grant-options.password.username=alice
quarkus.oidc-client.grant-options.password.password=alice

# Configure REST clients

%prod.port=8080
%dev.port=8080
%test.port=8081

org.acme.security.openid.connect.client.RestClientWithOidcClientFilter/mp-rest/url=http://localhost:${port}/protected
org.acme.security.openid.connect.client.RestClientWithTokenHeaderParam/mp-rest/url=http://localhost:${port}/protected
org.acme.security.openid.connect.client.RestClientWithTokenPropagationFilter/mp-rest/url=http://localhost:${port}/protected

前面的配置引用了 Keycloak,其中由 ProtectedResource 来验证传入的访问令牌,由 OidcClient 来使用 password 授权获取用户 alice 的令牌。两个 REST 客户端都指向 “ProtectedResource” 的 HTTP 地址。

%prod. 配置文件前缀添加到 quarkus.oidc.auth-server-url 中,可确保在以开发或测试模式运行应用程序时,Dev Services for Keycloak 会为您启动一个容器。有关更多信息,请参阅 Running the application in dev mode 部分。

Starting and configuring the Keycloak server

在以开发或测试模式运行应用程序时,不要启动 Keycloak 服务器;Dev Services for Keycloak 会启动一个容器。有关更多信息,请参阅 Running the application in dev mode 部分。确保将 realm configuration file 放入 target/classes 目录中的类路径中。此放置可确保该文件在开发模式下自动导入。然而,如果您已经构建了一个 complete solution,则不需要将 realm 文件添加到类路径中,因为构建过程已经完成了该操作。

若要启动 Keycloak 服务器,可以使用 Docker 并只需运行以下命令:

docker run --name keycloak -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak:{keycloak.version} start-dev

{keycloak.version} 设置为 25.0.2 或更高版本。

您可以在 localhost:8180 访问您的 Keycloak 服务器。

admin 用户身份登录以访问 Keycloak 管理控制台。密码是 admin

导入 realm configuration file 以创建新领域。有关详细信息,请参阅 Keycloak 文档,了解如何 create a new realm

quarkus 领域文件添加了一个 frontend 客户端和 aliceadmin 用户。 alice 有一个 user 角色。 adminuseradmin 两个角色。

Running the application in dev mode

若要以开发人员模式运行该应用程序,请使用:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

Dev Services for Keycloak 启动 Keycloak 容器并导入 quarkus-realm.json

打开一个可从 /q/dev-ui 处获得的 Dev UI,然后单击 OpenID Connect Dev UI 卡中的 Provider: Keycloak 链接。

系统提示时,登录到 OpenID Connect Dev UI 提供的 Single Page Application

  • alice 用户身份登录,密码为 alice。此用户具有 user 角色。

    • 访问 /frontend/user-name-with-propagated-token,其中会返回 200

    • 访问 /frontend/admin-name-with-propagated-token,其中会返回 403

  • 使用密码 admin 退出并重新以 admin 用户身份登录。此用户具有 adminuser 两个角色。

    • 访问 /frontend/user-name-with-propagated-token,其中会返回 200

    • 访问 /frontend/admin-name-with-propagated-token,其中会返回 200

您已经测试过 FrontendResource 可以从 OpenID Connect Dev UI 传播访问令牌。

Running the application in JVM mode

在 dev 模式下探索应用程序后,您可以将其作为标准 Java 应用程序运行。

首先,对其进行编译:

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

然后,运行它:

java -jar target/quarkus-app/quarkus-run.jar

Running the application in native mode

你可以将此演示编译为本地代码;不需要任何修改。

这意味着你不再需要在你的生产环境中安装 JVM,因为运行时技术包含在生成的可执行文件中并经过优化,以便在最小资源下运行。

编译时间较长,因此此步骤默认关闭。要再次构建,请启用 native 配置文件:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

一段时间后,构建完成后,你可以直接运行本地可执行文件:

./target/security-openid-connect-quickstart-1.0.0-SNAPSHOT-runner

Testing the application

有关在开发模式下测试你的应用程序的更多信息,请参阅前面的 Running the application in dev mode 部分。

你可以使用 curl 测试在 JVM 或 Native 模式下启动的应用程序。

获取 alice 的访问令牌:

export access_token=$(\
    curl --insecure -X POST http://localhost:8180/realms/quarkus/protocol/openid-connect/token \
    --user backend-service:secret \
    -H 'content-type: application/x-www-form-urlencoded' \
    -d 'username=alice&password=alice&grant_type=password' | jq --raw-output '.access_token' \
 )

使用此令牌调用 /frontend/user-name-with-propagated-token。此命令返回 200 状态码和名称 alice

curl -i -X GET \
  http://localhost:8080/frontend/user-name-with-propagated-token \
  -H "Authorization: Bearer "$access_token

使用相同的令牌调用 /frontend/admin-name-with-propagated-token。与前面的命令相反,此命令返回 403,因为 alice 仅有 user 角色:

curl -i -X GET \
  http://localhost:8080/frontend/admin-name-with-propagated-token \
  -H "Authorization: Bearer "$access_token

接下来,获取 admin 的访问令牌:

export access_token=$(\
    curl --insecure -X POST http://localhost:8180/realms/quarkus/protocol/openid-connect/token \
    --user backend-service:secret \
    -H 'content-type: application/x-www-form-urlencoded' \
    -d 'username=admin&password=admin&grant_type=password' | jq --raw-output '.access_token' \
 )

使用此令牌调用 /frontend/user-name-with-propagated-token。此命令返回 200 状态码和名称 admin

curl -i -X GET \
  http://localhost:8080/frontend/user-name-with-propagated-token \
  -H "Authorization: Bearer "$access_token

使用相同的令牌调用 /frontend/admin-name-with-propagated-token。此命令还返回 200 状态码和名称 admin,因为 admin 同时具有 useradmin 角色:

curl -i -X GET \
  http://localhost:8080/frontend/admin-name-with-propagated-token \
  -H "Authorization: Bearer "$access_token

接下来,查看 FrontendResource 方法,该方法不会传播现有令牌,而是使用 OidcClient 来获取并传播令牌。如已展示,OidcClient 被配置为获取 alice 用户的令牌。

curl -i -X GET \
  http://localhost:8080/frontend/user-name-with-oidc-client-token

此命令返回 200 状态码和名称 alice

curl -i -X GET \
  http://localhost:8080/frontend/admin-name-with-oidc-client-token

与此前命令相反,此命令返回 403 状态码。

接下来,测试以编程方式创建的 OIDC 客户端是否在响应式和命令式(阻塞)模式中正确获取并传播令牌。

调用 /user-name-with-oidc-client-token-header-param。此命令返回 200 状态码和名称 alice

curl -i -X GET \
  http://localhost:8080/frontend/user-name-with-oidc-client-token-header-param

调用 /admin-name-with-oidc-client-token-header-param。与此前命令相反,此命令返回 403 状态码:

curl -i -X GET \
  http://localhost:8080/frontend/admin-name-with-oidc-client-token-param

接下来,测试在阻塞模式下使用 OIDC 客户端的端点。

调用 /user-name-with-oidc-client-token-header-param-blocking。此命令返回 200`状态代码和名称 `alice:

curl -i -X GET \
  http://localhost:8080/frontend/user-name-with-oidc-client-token-header-param-blocking

调用 /admin-name-with-oidc-client-token-header-param-blocking。与前一个命令相反,此命令返回 `403`状态代码:

curl -i -X GET \
  http://localhost:8080/frontend/admin-name-with-oidc-client-token-param-blocking