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()); } }
ProtectedResource
返回一个来自 userName ()
和 adminName ()
方法的名称。名称从当前的 JsonWebToken
中提取。
接下来,添加两个 REST 客户端: OidcClientRequestReactiveFilter
和 AccessTokenRequestReactiveFilter
,其 FrontendResource
用来调用 ProtectedResource
。
添加 OidcClientRequestReactiveFilter
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.reactive.filter.OidcClientRequestReactiveFilter; import io.smallrye.mutiny.Uni; @RegisterRestClient @RegisterProvider(OidcClientRequestReactiveFilter.class) @Path("/") public interface RestClientWithOidcClientFilter { @GET @Produces("text/plain") @Path("userName") Uni<String> getUserName(); @GET @Produces("text/plain") @Path("adminName") Uni<String> getAdminName(); }
RestClientWithOidcClientFilter
接口依赖于 OidcClientRequestReactiveFilter
来获取和传播令牌。
添加 AccessTokenRequestReactiveFilter
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.reactive.AccessTokenRequestReactiveFilter; import io.smallrye.mutiny.Uni; @RegisterRestClient @RegisterProvider(AccessTokenRequestReactiveFilter.class) @Path("/") public interface RestClientWithTokenPropagationFilter { @GET @Produces("text/plain") @Path("userName") Uni<String> getUserName(); @GET @Produces("text/plain") @Path("adminName") Uni<String> getAdminName(); }
RestClientWithTokenPropagationFilter
接口依赖于 AccessTokenRequestReactiveFilter
来传播传入的已存在的令牌。
请注意,RestClientWithOidcClientFilter
和 RestClientWithTokenPropagationFilter
接口都是相同的。这是因为在同一 REST 客户端上组合 OidcClientRequestReactiveFilter
和 AccessTokenRequestReactiveFilter
会导致副作用,因为两个过滤器可能会相互干扰。例如,OidcClientRequestReactiveFilter
可以覆盖 AccessTokenRequestReactiveFilter
传播的令牌,或者 AccessTokenRequestReactiveFilter
可能会在没有令牌可用时被调用,并且 OidcClientRequestReactiveFilter
会改为获取新令牌。
现在,通过添加 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 org.eclipse.microprofile.rest.client.inject.RestClient; import io.smallrye.mutiny.Uni; @Path("/frontend") public class FrontendResource { @Inject @RestClient RestClientWithOidcClientFilter restClientWithOidcClientFilter; @Inject @RestClient RestClientWithTokenPropagationFilter restClientWithTokenPropagationFilter; @GET @Path("user-name-with-oidc-client-token") @Produces("text/plain") public Uni<String> getUserNameWithOidcClientToken() { return restClientWithOidcClientFilter.getUserName(); } @GET @Path("admin-name-with-oidc-client-token") @Produces("text/plain") public Uni<String> getAdminNameWithOidcClientToken() { return restClientWithOidcClientFilter.getAdminName(); } @GET @Path("user-name-with-propagated-token") @Produces("text/plain") public Uni<String> getUserNameWithPropagatedToken() { return restClientWithTokenPropagationFilter.getUserName(); } @GET @Path("admin-name-with-propagated-token") @Produces("text/plain") public Uni<String> getAdminNameWithPropagatedToken() { return restClientWithTokenPropagationFilter.getAdminName(); } }
FrontendResource
使用带有 OIDC 令牌传播 Reactive 过滤器的 REST 客户端,在 /frontend/user-name-with-oidc-client-token
或 /frontend/admin-name-with-oidc-client-token
被调用时,获取并传播访问令牌到 ProtectedResource
。另外,FrontendResource
使用带有 OpenID Connect Token Propagation Reactive Filter
的 REST 客户端在 /frontend/user-name-with-propagated-token
或 /frontend/admin-name-with-propagated-token
被调用时将当前的传入访问令牌传播到 ProtectedResource
。
最后,添加 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
。如果没有此映射程序,Easy Reactive
可以正确地将来自 REST 客户端调用的转义转换为 500
,以避免泄漏下游资源(如 ProtectedResource
)的信息。但是,在测试中,无法断言 500
是由授权异常导致的,而不是一些内部错误。