Miguel Almeida
Miguel Almeida

Reputation: 214

Best way to share data between JAX-RS request filters

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

Upvotes: 4

Views: 1579

Answers (1)

cassiomolin
cassiomolin

Reputation: 131067

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();

Upvotes: 2

Related Questions