2.5. 编写应用程序


首先实施 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());
    }
}
Copy to Clipboard Toggle word wrap

ProtectedResource 返回一个来自 userName ()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.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();
}
Copy to Clipboard Toggle word wrap
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); 
2

}
Copy to Clipboard Toggle word wrap
1 2
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.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();
}
Copy to Clipboard Toggle word wrap
1
使用 REST 客户端注册 OIDC 令牌传播过滤器,以传播传入的已存在的令牌。
重要

不要在同一 REST 客户端中使用 RestClientWithOidcClientFilterRestClientWithTokenPropagationFilter 接口,因为它们可能会发生冲突,从而导致问题。例如,OIDC 客户端过滤器可以从 OIDC 令牌传播过滤器覆盖令牌,或者传播过滤器在 none 可用时尝试传播令牌,则希望 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);
    }
}
Copy to Clipboard Toggle word wrap
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(); 
3

    @Inject
    @RestClient
    RestClientWithTokenHeaderParam restClientWithTokenHeaderParam; 
4


    @GET
    @Path("user-name-with-oidc-client-token")
    @Produces("text/plain")
    public Uni<String> getUserNameWithOidcClientToken() { 
5

        return restClientWithOidcClientFilter.getUserName();
    }

    @GET
    @Path("admin-name-with-oidc-client-token")
    @Produces("text/plain")
    public Uni<String> getAdminNameWithOidcClientToken() { 
6

	return restClientWithOidcClientFilter.getAdminName();
    }

    @GET
    @Path("user-name-with-propagated-token")
    @Produces("text/plain")
    public Uni<String> getUserNameWithPropagatedToken() { 
7

        return restClientWithTokenPropagationFilter.getUserName();
    }

    @GET
    @Path("admin-name-with-propagated-token")
    @Produces("text/plain")
    public Uni<String> getAdminNameWithPropagatedToken() { 
8

        return restClientWithTokenPropagationFilter.getAdminName();
    }

    @GET
    @Path("user-name-with-oidc-client-token-header-param")
    @Produces("text/plain")
    public Uni<String> getUserNameWithOidcClientTokenHeaderParam() { 
9

    	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() { 
10

    	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() { 
11

    	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() { 
12

    	Tokens tokens = tokenHelper.getTokens(oidcClientCreator.getOidcClient()).await().indefinitely();
        return restClientWithTokenHeaderParam.getAdminName("Bearer " + tokens.getAccessToken()).await().indefinitely();
    }

}
Copy to Clipboard Toggle word wrap
1 5 6
FrontendResource 使用带有 OIDC 客户端过滤器注入的 RestClientWithOidcClientFilter REST 客户端来获取并传播访问令牌到 ProtectedResource (当 /frontend/user-name-with-oidc-client-token/frontend/admin-name-with-oidc-client-token )。
2 7 8
FrontendResource 使用注入的 RestClientWithTokenPropagationFilter REST 客户端以及 OIDC 令牌传播过滤器,以便在 /frontend/user-name-with-propagated-token/frontend/admin-name-with-propagated-token 被调用时将当前的传入访问令牌传播到 ProtectedResource
4 9 10
FrontendResource 使用以编程方式创建的 OIDC 客户端来获取和传播访问令牌到 ProtectedResource,方法是将其直接传递给注入的 RestClientWithTokenHeaderParam REST 客户端方法作为 HTTP Authorization 标头值,即 /frontend/user-name-with-oidc-client-token-header-param/frontend/admin-name-with-oidc-client-token-header-param
11 12
有时,在向令牌传播令牌之前,可能需要以阻止方式获取令牌。本例演示了如何在这样的情形中获取令牌。
3
当 OIDC 客户端直接使用时,io.quarkus.oidc.client.runtime.TokensHelper 是一个非常有用的工具,没有 OIDC 客户端过滤器。要使用 TokensHelper,请将 OIDC Client 传递给它以获取令牌,并且 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();
	}

}
Copy to Clipboard Toggle word wrap

此例外映射程序仅用于在测试期间验证 ProtectedResource 是否在令牌没有预期的角色时返回 403。如果没有此映射程序,Quarkus REST (以前称为 RESTEasy Reactive)可以正确地将来自 REST 客户端调用的异常转换为 500,以避免泄漏下游资源(如 ProtectedResource )的信息。但是,在测试中,无法断言 500 是由授权异常导致的,而不是一些内部错误。

返回顶部
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2025 Red Hat