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 から展開されます。
次に、FrontendResource が ProtectedResource を呼び出すために使用する 2 つの REST クライアント OidcClientRequestReactiveFilter および AccessTokenRequestReactiveFilter を追加します。
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 Client 上で OidcClientRequestReactiveFilter および AccessTokenRequestReactiveFilter を組み合わせると、両方のフィルターが相互に干渉する可能性があるため、副作用が発生するためです。たとえば、OidcClientRequestReactiveFilter は、AccessTokenRequestReactiveFilter によって伝播されたトークンをオーバーライドできます。また、伝播できるトークンがなく、代わりに OidcClientRequestReactiveFilter が新しいトークンを取得することが予想されるときにそれが呼び出されると、AccessTokenRequestReactiveFilter が失敗する可能性があります。
次に、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 は、/frontend/user-name-with-oidc-client-token または /frontend/admin-name-with-oidc-client-token のいずれかが呼び出されたときに、OIDC トークン伝播 Reactive フィルターを備えた REST クライアントを使用して、ProtectedResource へのアクセストークンを取得して伝播します。また、FrontendResource は、/frontend/user-name-with-propagated-token または /frontend/admin-name-with-propagated-token のいずれかが呼び出されたときに OpenID Connect Token Propagation Reactive Filter を備えた REST Client を使用して、現在の受信アクセストークンを 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 を返すことをテスト中に検証するためにのみ追加されます。このマッパーがなければ、RESTEasy Reactive は、REST クライアント呼び出しから逃れた例外を 500 に正しく変換し、ProtectedResource などのダウンストリームリソースからの情報の漏洩を回避します。ただし、テストでは、500 が何らかの内部エラーではなく、認可例外によって発生したと断言することはできません。