Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a version of @OidcClientFilter for the reactive client #28451

Merged
merged 4 commits into from Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -379,7 +379,28 @@ Note it will also bring `io.quarkus:quarkus-oidc-client`.

It works similarly to the way `OidcClientRequestFilter` does (see <<oidc-client-filter, Use OidcClient in MicroProfile RestClient client filter>>) - it uses `OidcClient` to acquire the access token, refresh it if needed, and set it as an HTTP `Authorization` `Bearer` scheme value. The difference is that it works with xref:rest-client-reactive.adoc[Reactive RestClient] and implements a non-blocking client filter which does not block the current IO thread when acquiring or refreshing the tokens.

`OidcClientRequestReactiveFilter` delays an initial token acquisition until it is executed to avoid blocking an IO thread, and it currently can only be registered with `org.eclipse.microprofile.rest.client.annotation.RegisterProvider` annotation:
`OidcClientRequestReactiveFilter` delays an initial token acquisition until it is executed to avoid blocking an IO thread.

You can selectively register `OidcClientRequestReactiveFilter` by using either io.quarkus.oidc.client.reactive.filter.OidcClientFilter or org.eclipse.microprofile.rest.client.annotation.RegisterProvider annotations:

[source,java]
----
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.reactive.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;

@RegisterRestClient
@OidcClientFilter
@Path("/")
public interface ProtectedResourceService {

@GET
Uni<String> getUserName();
}
----

or

[source,java]
----
Expand Down
@@ -1,17 +1,46 @@
package io.quarkus.oidc.client.reactive.filter.deployment;

import java.util.Collection;
import java.util.List;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Type;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.oidc.client.deployment.OidcClientBuildStep.IsEnabled;
import io.quarkus.oidc.client.reactive.filter.OidcClientFilter;
import io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter;
import io.quarkus.rest.client.reactive.deployment.DotNames;
import io.quarkus.rest.client.reactive.deployment.RegisterProviderAnnotationInstanceBuildItem;

@BuildSteps(onlyIf = IsEnabled.class)
public class OidcClientReactiveFilterBuildStep {

private static final DotName OIDC_CLIENT_FILTER = DotName.createSimple(OidcClientFilter.class.getName());
private static final DotName OIDC_CLIENT_REQUEST_REACTIVE_FILTER = DotName
.createSimple(OidcClientRequestReactiveFilter.class.getName());

// we simply pretend that @OidcClientFilter means @RegisterProvider(OidcClientRequestReactiveFilter.class)
@BuildStep
void oidcClientFilterSupport(CombinedIndexBuildItem indexBuildItem,
BuildProducer<RegisterProviderAnnotationInstanceBuildItem> producer) {
Collection<AnnotationInstance> instances = indexBuildItem.getIndex().getAnnotations(OIDC_CLIENT_FILTER);
for (AnnotationInstance instance : instances) {
String targetClass = instance.target().asClass().name().toString();
producer.produce(new RegisterProviderAnnotationInstanceBuildItem(targetClass, AnnotationInstance.create(
DotNames.REGISTER_PROVIDER, instance.target(), List.of(AnnotationValue.createClassValue("value",
Type.create(OIDC_CLIENT_REQUEST_REACTIVE_FILTER, org.jboss.jandex.Type.Kind.CLASS))))));
}
}

@BuildStep
void registerProvider(BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
Expand Down
@@ -0,0 +1,13 @@
package io.quarkus.oidc.client.reactive.filter;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OidcClientFilter {
}
Expand Up @@ -4,7 +4,6 @@
import java.util.function.Consumer;

import javax.annotation.Priority;
import javax.inject.Inject;
import javax.ws.rs.Priorities;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
Expand All @@ -24,7 +23,6 @@ public class OidcClientRequestReactiveFilter extends AbstractTokensProducer impl
private static final Logger LOG = Logger.getLogger(OidcClientRequestReactiveFilter.class);
private static final String BEARER_SCHEME_WITH_SPACE = OidcConstants.BEARER_SCHEME + " ";

@Inject
@ConfigProperty(name = "quarkus.oidc-client-reactive-filter.client-name")
Optional<String> clientName;

Expand All @@ -38,22 +36,22 @@ protected void initTokens() {
public void filter(ResteasyReactiveClientRequestContext requestContext) {
requestContext.suspend();

super.getTokens().subscribe().with(new Consumer<Tokens>() {
super.getTokens().subscribe().with(new Consumer<>() {
@Override
public void accept(Tokens tokens) {
requestContext.getHeaders().putSingle(HttpHeaders.AUTHORIZATION,
BEARER_SCHEME_WITH_SPACE + tokens.getAccessToken());
requestContext.resume();
}
}, new Consumer<Throwable>() {
}, new Consumer<>() {
@Override
public void accept(Throwable t) {
if (t instanceof DisabledOidcClientException) {
LOG.debug("Client is disabled");
requestContext.abortWith(Response.status(500).build());
requestContext.abortWith(Response.status(Response.Status.INTERNAL_SERVER_ERROR).build());
} else {
LOG.debugf("Access token is not available, aborting the request with HTTP 401 error: %s", t.getMessage());
requestContext.abortWith(Response.status(401).build());
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
requestContext.resume();
}
Expand Down
@@ -0,0 +1,31 @@
package io.quarkus.rest.client.reactive.deployment;

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.jboss.jandex.AnnotationInstance;

import io.quarkus.builder.item.MultiBuildItem;

/**
* A Build Item that is used to capture the information of usages equivalent to {@code @RegisterProvider(SomeProvider.class)}.
* The use of the build item facilitates support for use cases that need to have the same effect as
* {@code @RegisterProvider(SomeProvider.class)},
* but that don't actually use the {@link RegisterProvider} annotation.
*/
public final class RegisterProviderAnnotationInstanceBuildItem extends MultiBuildItem {

private final String targetClass;
private final AnnotationInstance annotationInstance;

public RegisterProviderAnnotationInstanceBuildItem(String targetClass, AnnotationInstance annotationInstance) {
this.targetClass = targetClass;
this.annotationInstance = annotationInstance;
}

public String getTargetClass() {
return targetClass;
}

public AnnotationInstance getAnnotationInstance() {
return annotationInstance;
}
}
Expand Up @@ -206,6 +206,27 @@ void registerHeaderFactoryBeans(CombinedIndexBuildItem index,
}
}

@BuildStep
public void registerProvidersInstances(CombinedIndexBuildItem indexBuildItem,
BuildProducer<RegisterProviderAnnotationInstanceBuildItem> producer) {
IndexView index = indexBuildItem.getIndex();

for (AnnotationInstance annotation : index.getAnnotations(REGISTER_PROVIDER)) {
String targetClass = annotation.target().asClass().name().toString();
producer.produce(new RegisterProviderAnnotationInstanceBuildItem(targetClass, annotation));
}

for (AnnotationInstance annotation : index.getAnnotations(REGISTER_PROVIDERS)) {
String targetClass = annotation.target().asClass().name().toString();
AnnotationInstance[] nestedArray = annotation.value().asNestedArray();
if ((nestedArray != null) && nestedArray.length > 0) {
for (AnnotationInstance nestedInstance : nestedArray) {
producer.produce(new RegisterProviderAnnotationInstanceBuildItem(targetClass, nestedInstance));
}
}
}
}

/**
* Creates an implementation of `AnnotationRegisteredProviders` class with a constructor that:
* <ul>
Expand All @@ -221,6 +242,7 @@ void registerHeaderFactoryBeans(CombinedIndexBuildItem index,
*/
@BuildStep
void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem,
List<RegisterProviderAnnotationInstanceBuildItem> registerProviderAnnotationInstances,
BuildProducer<GeneratedBeanBuildItem> generatedBeans,
BuildProducer<GeneratedClassBuildItem> generatedClasses,
BuildProducer<UnremovableBeanBuildItem> unremovableBeans,
Expand All @@ -230,16 +252,9 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem,
IndexView index = indexBuildItem.getIndex();
Map<String, List<AnnotationInstance>> annotationsByClassName = new HashMap<>();

for (AnnotationInstance annotation : index.getAnnotations(REGISTER_PROVIDER)) {
String targetClass = annotation.target().asClass().name().toString();
annotationsByClassName.computeIfAbsent(targetClass, key -> new ArrayList<>())
.add(annotation);
}

for (AnnotationInstance annotation : index.getAnnotations(REGISTER_PROVIDERS)) {
String targetClass = annotation.target().asClass().name().toString();
annotationsByClassName.computeIfAbsent(targetClass, key -> new ArrayList<>())
.addAll(asList(annotation.value().asNestedArray()));
for (RegisterProviderAnnotationInstanceBuildItem bi : registerProviderAnnotationInstances) {
annotationsByClassName.computeIfAbsent(bi.getTargetClass(), key -> new ArrayList<>())
.add(bi.getAnnotationInstance());
}

try (ClassCreator classCreator = ClassCreator.builder()
Expand Down
Expand Up @@ -4,14 +4,13 @@
import javax.ws.rs.Path;
import javax.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.quarkus.oidc.client.reactive.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;

@RegisterRestClient
@RegisterProvider(OidcClientRequestReactiveFilter.class)
@OidcClientFilter
@Path("/")
public interface ProtectedResourceServiceReactiveFilter {

Expand Down
Expand Up @@ -2,8 +2,13 @@ package io.quarkus.it.resteasy.reactive.kotlin

import org.jboss.resteasy.reactive.RestHeader
import javax.ws.rs.GET
import javax.ws.rs.POST
import javax.ws.rs.Path
import javax.ws.rs.PathParam
import javax.ws.rs.core.Context
import javax.ws.rs.core.HttpHeaders
import javax.ws.rs.core.Response
import javax.ws.rs.core.UriInfo

@Path("/greeting")
class GreetingResource(val headers: HttpHeaders) {
Expand All @@ -18,6 +23,10 @@ class GreetingResource(val headers: HttpHeaders) {
@Path("noop")
suspend fun noop() {
}

@POST
@Path("body/{name}")
suspend fun body(@PathParam(value = "name") name: String, greeting: Greeting, @Context uriInfo: UriInfo) = Response.ok(greeting).build()
}

data class Greeting(val message: String)