4

I want to validate a user making a request. For that I have two filters: AuthenticationFilter and AuthorizationFilter. AuthenticationFilter extracts a token from the request and finds the user from the database. AuthorizationFilter checks if that user (retrieved by the previous filter) has the necessary permissions. I have two possible solutions and would like to know the pros and cons of each one and which should I use. I would also need to access the user in the actual business logic. My code is as follows:

AuthenticationFilter:

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    // option 1.1
    @Inject
    @AuthenticatedUser
    private Event<User> authenticatedUserEvent;

    // option 1.2
    @Inject
    @AuthenticatedUser
    private User authenticatedUser;

    public void filter(ContainerRequestContext requestContext) throws IOException {
        String token = getToken(requestContext);
        User user = getUser(token)
        if (user == null) {
            requestContext.abortWith(...);
        } else {
            // option 1.1
            authenticatedUserEvent.fire(User);

            // option 1.2
            authenticatedUser.setData(user);

            // option 2
            requestContext.setProperty("authenticatedUser", user);
        }
    }
}

AuthorizationFilter:

@Secured
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {

    @Context
    private ResourceInfo resourceInfo;

    // option 1
    @Inject
    @AuthenticatedUser
    private User authenticatedUser;

    public void filter(ContainerRequestContext requestContext) throws IOException {

        // option 2
        User authenticatedUser = (User) requestContext.getProperty("authenticatedUser")

        boolean allowed = verifyRoles(user, resourceInfo.getResourceClass(), resourceInfo.getResourceMethod());
        if (!allowed) {
            requestContext.abortWith(...);
        }
    }
}

AuthenticatedUserProducer (only for option 1):

@RequestScoped
public class AuthenticatedUserProducer {

    @Produces
    @RequestScoped
    @AuthenticatedUser
    private User authenticatedUser;

    public void handleAuthenticationEvent(@Observes @AuthenticatedUser User user) {
        this.authenticatedUser = user
    }
}

For option 1, is it necessary to annotate the filters with @RequestScoped? By default filters are application scoped, so is the injection safe? I.e., the event fired by filter 1 in request chain 1 will not end up injected in filter 2 of chain 2, if more than one request is being processed at the same time? And the same when injecting the user in the resource class where the actual business logic runs?

For option 2, I no longer have access to ContainerRequestContext, but I can access the object by injecting the HttpServletRequest. But this option seems to me "less clean" because I have to cast the stored objects to User before using them, when with the injection approach I can use the objects directly.

I already checked these questions, what I'm looking for is to determine which option is the best:

I'm currently using WildFly 11, with its default implementation of JAX-RS (Resteasy) and CDI (Weld).

Miguel Almeida
  • 204
  • 1
  • 4
  • 15
  • After some testing I determined that the injection is safe only if the bean (or the producer field) is marked `@RequestScoped`. This is independent of whether the filters are marked as `@RequestScoped` (per-request scope) or not (singleton). – Miguel Almeida Jul 01 '18 at 16:14

1 Answers1

2

Once you are performing authentication/authorization, you could use SecurityContext, which is part of the JAX-RS API. See an example of how to use it in your authentication filter:

final SecurityContext currentSecurityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {

        @Override
        public Principal getUserPrincipal() {
            // Return a Principal instance according to your needs
            return () -> username;
        }

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

    @Override
    public boolean isSecure() {
        return currentSecurityContext.isSecure();
    }

    @Override
    public String getAuthenticationScheme() {
        // Return the authentication scheme used by your application
        return SecurityContext.BASIC_AUTH;
    }
});

Then inject the SecurityContext in your JAX-RS resource and providers with the @Context annotation:

@Context
SecurityContext securityContext;

Then you can get the Principal instance which was set in the SecurityContext:

Principal principal = securityContext.getUserPrincipal();
cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • Is there a way to use the provided `SecurityContext` without creating my own implementation, or is the default `SecurityContext` just a placeholder? – Miguel Almeida Jun 28 '18 at 15:49
  • @MiguelAlmeida `SecurityContext` is an interface and the JAX-RS API doesn't provide any default implementation. You can create yours. – cassiomolin Jun 28 '18 at 16:01
  • Thank you for your answer. As this is for an academic project, and I don't have the time to understand what I need about the security context and principals, I ended up just using the option 1.2 of my original question. – Miguel Almeida Jul 01 '18 at 16:10