OpenID Connect client and token propagation quickstart

了解如何在应用程序中使用 OpenID Connect (OIDC) 和 OAuth2 客户端以及过滤器来获取、刷新和传播访问令牌。

Learn how to use OpenID Connect (OIDC) and OAuth2 clients with filters to get, refresh, and propagate access tokens in your applications.

有关 Quarkus 中 OIDC ClientToken Propagation 支持的更多信息,请参阅 OpenID Connect (OIDC) and OAuth2 client and filters reference guide

For more information about OIDC Client and Token Propagation support in Quarkus, see the OpenID Connect (OIDC) and OAuth2 client and filters reference guide.

若要使用带有人员令牌授权来保护应用程序,请参阅 OpenID Connect (OIDC) Bearer token authentication 指南。

To protect your applications by using Bearer Token Authorization, see the OpenID Connect (OIDC) Bearer token authentication guide.

Prerequisites

Unresolved directive in security-openid-connect-client.adoc - include::{includes}/prerequisites.adoc[]* jq tool

Unresolved directive in security-openid-connect-client.adoc - include::{includes}/prerequisites.adoc[] * jq tool

Architecture

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

In this example, an application is built with two Jakarta REST resources, FrontendResource and ProtectedResource. Here, FrontendResource uses one of three methods to propagate access tokens to ProtectedResource:

  • It can get a token by using an OIDC client filter before propagating it.

  • It can get a token by using a programmatically created OIDC client and propagate it by passing it to a REST client method as an HTTP Authorization header value.

  • It can use an OIDC token propagation filter to propagate the incoming access token.

FrontendResource 有 8 个端点:

FrontendResource has eight endpoints:

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

When either /frontend/user-name-with-oidc-client-token or /frontend/admin-name-with-oidc-client-token endpoint is called, FrontendResource uses a REST client with an OIDC client filter to get and propagate an access token to ProtectedResource . When either /frontend/user-name-with-oidc-client-token-header-param or /frontend/admin-name-with-oidc-client-token-header-param endpoint is called, FrontendResource uses a programmatically created OIDC client to get and propagate an access token to ProtectedResource by passing it to a REST client method as an HTTP Authorization header value. When either /frontend/user-name-with-propagated-token or /frontend/admin-name-with-propagated-token endpoint is called, FrontendResource uses a REST client with OIDC Token Propagation Filter to propagate the current incoming access token to ProtectedResource.

ProtectedResource 有两个端点:

ProtectedResource has two endpoints:

  • /protected/user-name

  • /protected/admin-name

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

Both endpoints return the username extracted from the incoming access token, which was propagated to ProtectedResource from FrontendResource. The only difference between these endpoints is that calling /protected/user-name is only allowed if the current access token has a user role, and calling /protected/admin-name is only allowed if the current access token has an admin role.

Solution

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

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

克隆 Git 存储库: git clone {quickstarts-clone-url},或下载 {quickstarts-archive-url}[存档]。

Clone the Git repository: git clone {quickstarts-clone-url}, or download an {quickstarts-archive-url}[archive].

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

The solution is in the security-openid-connect-client-quickstart directory.

Creating the Maven project

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

First, you need a new project. Create a new project with the following command:

Unresolved directive in security-openid-connect-client.adoc - include::{includes}/devtools/create-app.adoc[]

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

It generates a Maven project, importing the oidc, rest-client-oidc-filter, rest-client-oidc-token-propagation, and rest extensions.

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

If you already have your Quarkus project configured, you can add these extensions to your project by running the following command in your project base directory:

Unresolved directive in security-openid-connect-client.adoc - include::{includes}/devtools/extension-add.adoc[]

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

It adds the following extensions to your build file:

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

Start by implementing 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 中提取。

ProtectedResource returns a name from both userName() and adminName() methods. The name is extracted from the current JsonWebToken.

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

Next, add the following REST clients:

  1. RestClientWithOidcClientFilter, which uses an OIDC client filter provided by the quarkus-rest-client-oidc-filter extension to get and propagate an access token.

  2. RestClientWithTokenHeaderParam, which accepts a token already acquired by the programmatically created OidcClient as an HTTP Authorization header value.

  3. RestClientWithTokenPropagationFilter, which uses an OIDC token propagation filter provided by the quarkus-rest-client-oidc-token-propagation extension to get and propagate an access token.

添加 RestClientWithOidcClientFilter REST 客户端:

Add the RestClientWithOidcClientFilter REST client:

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 Register an OIDC client filter with the REST client to get and propagate the tokens.

添加 RestClientWithTokenHeaderParam REST 客户端:

Add the RestClientWithTokenHeaderParam 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); 1

    @GET
    @Produces("text/plain")
    @Path("adminName")
    Uni<String> getAdminName(@HeaderParam("Authorization") String authorization); 1
}
1 RestClientWithTokenHeaderParam REST client expects that the tokens will be passed to it as HTTP Authorization header values.

添加 RestClientWithTokenPropagationFilter REST 客户端:

Add the RestClientWithTokenPropagationFilter REST client:

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 Register an OIDC token propagation filter with the REST client to propagate the incoming already-existing tokens.

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

Do not use the RestClientWithOidcClientFilter and RestClientWithTokenPropagationFilter interfaces in the same REST client because they can conflict, leading to issues. For example, the OIDC client filter can override the token from the OIDC token propagation filter, or the propagation filter might not work correctly if it attempts to propagate a token when none is available, expecting the OIDC client filter to obtain a new token instead.

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

Also, add OidcClientCreator to create an OIDC client programmatically at startup. OidcClientCreator supports RestClientWithTokenHeaderParam REST client calls:

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 can be used to retrieve the already initialized, named OIDC clients and create new OIDC clients on demand.

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

Now, finish creating the application by adding 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 uses the injected RestClientWithOidcClientFilter REST client with the OIDC client filter to get and propagate an access token to ProtectedResource when either /frontend/user-name-with-oidc-client-token or /frontend/admin-name-with-oidc-client-token is called.
2 FrontendResource uses the injected RestClientWithTokenPropagationFilter REST client with the OIDC token propagation filter to propagate the current incoming access token to ProtectedResource when either /frontend/user-name-with-propagated-token or /frontend/admin-name-with-propagated-token is called.
3 FrontendResource uses the programmatically created OIDC client to get and propagate an access token to ProtectedResource by passing it directly to the injected RestClientWithTokenHeaderParam REST client’s method as an HTTP Authorization header value, when either /frontend/user-name-with-oidc-client-token-header-param or /frontend/admin-name-with-oidc-client-token-header-param is called.
4 Sometimes, one may have to acquire tokens in a blocking manner before propagating them with the REST client. This example shows how to acquire the tokens in such cases.
5 io.quarkus.oidc.client.runtime.TokensHelper is a useful tool when OIDC client is used directly, without the OIDC client filter. To use TokensHelper, pass OIDC Client to it to get the tokens and TokensHelper acquires the tokens and refreshes them if necessary in a thread-safe way.

最后,添加一个 Jakarta REST ExceptionMapper

Finally, add a 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 是由授权异常而不是某些内部错误引起的。

This exception mapper is only added to verify during the tests that ProtectedResource returns 403 when the token has no expected role. Without this mapper, Quarkus REST (formerly RESTEasy Reactive) would correctly convert the exceptions that escape from REST client calls to 500 to avoid leaking the information from the downstream resources such as ProtectedResource. However, in the tests, it would not be possible to assert that 500 is caused by an authorization exception instead of some internal error.

Configuring the application

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

Having prepared the code, you configure 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 地址。

The preceding configuration references Keycloak, which is used by ProtectedResource to verify the incoming access tokens and by OidcClient to get the tokens for a user alice by using a password grant. Both REST clients point to `ProtectedResource’s HTTP address.

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

Adding a %prod. profile prefix to quarkus.oidc.auth-server-url ensures that Dev Services for Keycloak launches a container for you when the application is run in dev or test modes. For more information, see the oidc-client-keycloak-dev-mode section.

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 文件添加到类路径中,因为构建过程已经完成了该操作。

Do not start the Keycloak server when you run the application in dev or test modes; Dev Services for Keycloak launches a container. For more information, see the oidc-client-keycloak-dev-mode section. Ensure you put the realm configuration file on the classpath, in the target/classes directory. This placement ensures that the file is automatically imported in dev mode. However, if you have already built a complete solution, you do not need to add the realm file to the classpath because the build process has already done so.

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

To start a Keycloak Server, you can use Docker and just run the following command:

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 或更高版本。

Set {keycloak.version} to 25.0.2 or later.

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

You can access your Keycloak Server at localhost:8180.

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

Log in as the admin user to access the Keycloak Administration Console. The password is admin.

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

Import the realm configuration file to create a new realm. For more details, see the Keycloak documentation about how to create a new realm.

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

This quarkus realm file adds a frontend client, and alice and admin users. alice has a user role. admin has both user and admin roles.

Running the application in dev mode

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

To run the application in a dev mode, use:

Unresolved directive in security-openid-connect-client.adoc - include::{includes}/devtools/dev.adoc[]

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

Dev Services for Keycloak launches a Keycloak container and imports quarkus-realm.json.

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

Open a Dev UI available at /q/dev-ui and click a Provider: Keycloak link in the OpenID Connect Dev UI card.

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

When asked, log in to a Single Page Application provided by the OpenID Connect Dev UI:

  • Log in as alice, with the password, alice. This user has a user role.

    • Access /frontend/user-name-with-propagated-token, which returns 200.

    • Access /frontend/admin-name-with-propagated-token, which returns 403.

  • Log out and back in as admin with the password, admin. This user has both admin and user roles.

    • Access /frontend/user-name-with-propagated-token, which returns 200.

    • Access /frontend/admin-name-with-propagated-token, which returns 200.

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

You have tested that FrontendResource can propagate the access tokens from the OpenID Connect Dev UI.

Running the application in JVM mode

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

After exploring the application in dev mode, you can run it as a standard Java application.

首先,对其进行编译:

First, compile it:

Unresolved directive in security-openid-connect-client.adoc - include::{includes}/devtools/build.adoc[]

然后,运行它:

Then, run it:

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

Running the application in native mode

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

You can compile this demo into native code; no modifications are required.

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

This implies that you no longer need to install a JVM on your production environment, as the runtime technology is included in the produced binary and optimized to run with minimal resources.

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

Compilation takes longer, so this step is turned off by default. To build again, enable the native profile:

Unresolved directive in security-openid-connect-client.adoc - include::{includes}/devtools/build-native.adoc[]

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

After a little while, when the build finishes, you can run the native binary directly:

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

Testing the application

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

For more information about testing your application in dev mode, see the preceding oidc-client-keycloak-dev-mode section.

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

You can test the application launched in JVM or Native modes with curl.

获取 alice 的访问令牌:

Obtain an access token for 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

Use this token to call /frontend/user-name-with-propagated-token. This command returns the 200 status code and the name 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 角色:

Use the same token to call /frontend/admin-name-with-propagated-token. In contrast to the preceding command, this command returns 403 because alice has only a user role:

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

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

Next, obtain an access token for 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

Use this token to call /frontend/user-name-with-propagated-token. This command returns a 200 status code and the name 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 角色:

Use the same token to call /frontend/admin-name-with-propagated-token. This command also returns the 200 status code and the name admin because admin has both user and admin roles:

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

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

Next, check the FrontendResource methods, which do not propagate the existing tokens but use OidcClient to get and propagate the tokens. As already shown, OidcClient is configured to get the tokens for the alice user.

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

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

This command returns the 200 status code and the name alice.

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

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

In contrast with the preceding command, this command returns a 403 status code.

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

Next, test that the programmatically created OIDC client correctly acquires and propagates the token with RestClientWithTokenHeaderParam both in reactive and imperative (blocking) modes.

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

Call the /user-name-with-oidc-client-token-header-param. This command returns the 200 status code and the name 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 状态码:

Call the /admin-name-with-oidc-client-token-header-param. In contrast with the preceding command, this command returns a 403 status code:

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

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

Next, test the endpoints which use OIDC client in in the blocking mode.

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

Call the /user-name-with-oidc-client-token-header-param-blocking. This command returns the 200 status code and the name 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`状态代码:

Call the /admin-name-with-oidc-client-token-header-param-blocking. In contrast with the preceding command, this command returns a 403 status code:

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