Search code examples
javasecurityjax-rsquarkus

How to retrieve SecurityContext in a Quarkus application?


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?


Solution

  • 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.