How to retrieve SecurityContext in a Quarkus application?

Viewed 2457

I have a Quarkus application in which I implemented the ContainerRequestFilter interface to save a header from incoming requests:

@PreMatching
public class SecurityFilter implements ContainerRequestFilter {
   private static final String HEADER_EMAIL = "HD-Email";

   @Override
   public void filter(ContainerRequestContext requestContext) throws IOException {
       String email = requestContext.getHeaders().getFirst(HEADER_EMAIL);

       if (email == null) {
           throw new AuthenticationFailedException("Email header is required");
       }

       requestContext.setSecurityContext(new SecurityContext() {
           @Override
           public Principal getUserPrincipal() {
               return () -> email;
           }

           @Override
           public boolean isUserInRole(String role) {
               return false;
           }

           @Override
           public boolean isSecure() {
               return false;
           }

           @Override
           public String getAuthenticationScheme() {
               return null;
           }
       });
   }
}

In a class annotated with ApplicationScoped I injected the context as follows:

@ApplicationScoped
public class ProjectService {
    @Context
    SecurityContext context;
    ...
}

The problem is that the context attribute is actually never injected, as it is always null.

What am I doing wrong? What should I do to be able to retrieve the SecurityContext throughout the application's code?

2 Answers

I like to abstract this problem, so that the business logic does not depend on JAX-RS-specific constructs. So, I create a class to describe my user, say User, and another interface, the AuthenticationContext, that holds the current user and any other authentication-related information I need, e.g.:

public interface AuthenticationContext {
    User getCurrentUser();
}

I create a RequestScoped implementation of this class, that also has the relevant setter(s):

@RequestScoped
public class AuthenticationContextImpl implements AuthenticationContext {
    private User user;

    @Override
    public User getCurrentUser() {
        return user;
    }

    public void setCurrentUser(User user) {
        this.user = user;
    }
}

Now, I inject this bean and the JAX-RS SecurityContext in a filter, that knows how to create the User and set it into my application-specific AuthenticationContext:

@PreMatching
public class SecurityFilter implements ContainerRequestFilter {

    @Inject AuthenticationContextImpl authCtx; // Injecting the implementation,
                                               // not the interface!!!

    @Context SecurityContext securityCtx;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        User user = ...// translate the securityCtx into a User
        authCtx.setCurrentUser(user);
    }
}

And then, any business bean that needs the user data, injects the environment-neutral, application-specific AuthenticationContext.

@Context can only be used in JAX-RS classes - i.e. classes annotated with @Path.

In your case, ProjectService is a CDI bean, not a JAX-RS class.

The canonical way to do what you want is to inject the SecurityContext into a JAX-RS resource and then pass that as a method parameter to your ProjectService

Related