Proactive authentication is enabled in Quarkus by default. This means that if an incoming request has a credential then that request will always be authenticated, even if the target page does not require authentication.
Requests with an invalid credential will always be rejected, even when the page is public.
You can change the default behavior if you only want to authenticate when the target page requires authentication.
To disable proactive authentication in Quarkus, set the following attribute in the application.properties
configuration file:
`quarkus.http.auth.proactive=false`
If you disable proactive authentication, the authentication process runs only when an identity is requested. An identity can be requested because of security rules that require the user to authenticate or because programmatic access to the current identity is required.
If proactive authentication is in use, accessing the SecurityIdentity
is a blocking operation.
This is because authentication may have yet to happen, and accessing it may require calls to external systems, such as databases that may block.
For blocking applications, this is no problem. However, if you have disabled authentication in a reactive application, this will fail (as you cannot do blocking operations on the IO thread).
To work around this, you need to @Inject
an instance of io.quarkus.security.identity.CurrentIdentityAssociation
and call the Uni<SecurityIdentity> getDeferredIdentity();
method.
You can then subscribe to the resulting Uni
and will be notified when authentication is complete and the identity is available.
Note
|
It’s still possible to access the SecurityIdentity synchronously with public SecurityIdentity getIdentity() in the RESTEasy Reactive from endpoints annotated with @RolesAllowed , @Authenticated , or with respective configuration authorization checks as authentication has already happened.
The same is also valid for the Reactive routes if a route response is synchronous.
|
Standard security annotations on CDI beans are not supported on IO thread if a non-void secured method returns a value synchronously and proactive authentication is disabled, as they need to access the SecurityIdentity
.
In the example below, we have defined HelloResource
and HelloService
. It’s easy to see that any GET request to /hello
will run on IO thread and throw BlockingOperationNotAllowedException
exception.
There is more than one way to fix the example:
-
switch to a worker thread (annotate
hello
endpoint with@Blocking
) -
change
sayHello
method return type (use reactive or asynchronous data type) -
arguably the safest way is to move
@RolesAllowed
annotation to the endpoint, as accessingSecurityIdentity
from endpoint methods is never the blocking operation
import jakarta.annotation.security.PermitAll;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.smallrye.mutiny.Uni;
@Path("/hello")
@PermitAll
public class HelloResource {
@Inject
HelloService helloService;
@GET
public Uni<String> hello() {
return Uni.createFrom().item(helloService.sayHello());
}
}
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class HelloService {
@RolesAllowed("admin")
public String sayHello() {
return "Hello";
}
}
You can use Jakarta REST ExceptionMapper
to capture Quarkus Security authentication exceptions such as io.quarkus.security.AuthenticationFailedException
, for example:
package io.quarkus.it.keycloak;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import io.quarkus.security.AuthenticationFailedException;
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFailedExceptionMapper implements ExceptionMapper<AuthenticationFailedException> {
@Context
UriInfo uriInfo;
@Override
public Response toResponse(AuthenticationFailedException exception) {
return Response.status(401).header("WWW-Authenticate", "Basic realm=\"Quarkus\"").build();
}
}
Caution
|
Some HTTP authentication mechanisms need to handle authentication exceptions themselves to create a correct authentication challenge.
For example, io.quarkus.oidc.runtime.CodeAuthenticationMechanism which manages OpenId Connect authorization code flow authentication, needs to build a correct redirect URL, cookies, etc.
For that reason, using custom exception mappers to customize authentication exceptions thrown by such mechanisms is not recommended.
Instead, a safer approach would be to ensure that proactive authentication is enabled and to use Vert.x HTTP route failure handlers. This is because events come to the handler with the correct response status and headers.
You will then only need to customize the response, as outlined in the following example:
|
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import io.quarkus.security.AuthenticationFailedException;
import io.vertx.core.Handler;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class AuthenticationFailedExceptionHandler {
public void init(@Observes Router router) {
router.route().failureHandler(new Handler<RoutingContext>() {
@Override
public void handle(RoutingContext event) {
if (event.failure() instanceof AuthenticationFailedException) {
event.response().end("CUSTOMIZED_RESPONSE");
} else {
event.next();
}
}
});
}
}