Authentication/authorization in JAX-RS using inter

2019-01-23 19:26发布

I am developing a new application in JavaEE 7 using WildFly 8. I am using JAX-RS to provide a RESTful service interface for remote applications.

Something like an HttpHeaders object can be injected in a resource method arguments using the @Context annotation. Since the object is based on request parameters (of course, the HTTP headers), I came up with the idea of creating my own injectable User object which is created based on the presence of a valid token in the request (something like an OAuth access token).

So, I want to achieve something like this:

@Path("/resources")
public class MyResource {

    @Path("/{id}")
    @GET
    public Response getById(@Context User user, @PathParam("id") long id) {
        ...
    }

}

Where User is an injectable object created based on request parameters, such as ones accessible through an HttpHeaders object. Of course, the provider can also throw an exception and return an HTTP error response if the User object cannot be created for any reason.

Now, my questions are:

  1. Is this a good design? If not, what better options do I have?
  2. How can I achieve this? I do not care if my solution is not JAX-RS specific and uses WildFly/RestEasy-specific internals, but I definitely prefer a portable solution if there exists one.

Thanks

2条回答
我命由我不由天
2楼-- · 2019-01-23 19:53

In my eyes this approach is valid as long as you don't try to build something like a session with this User Object.

As answered here you could use @Context and @Provider but that's not exactly what you want. Directly injecting a Class per @Context is possible with the Resteasy Dispatcher. But here you must register the Object which should be injected. I don't think that makes sense for request-scoped parameters. What you could do is inject a provider like this:

// Constructor of your JAX-RS Application
public RestApplication(@Context Dispatcher dispatcher) {
    dispatcher.getDefaultContextObjects().put(UserProvider.class, new UserProvider());
}

// a resource
public Response getById(@Context UserProvider userProvider) {
    User user = userProvider.get();
}

Other ways to solve your problem:

  1. Register a WebFilter, authenticate the user, wrap the ServletRequest and override getUserPrincipal. You can then access the UserPrincipal from a injected HttpServletRequest.
  2. Implement a JAX-RS Interceptor which implements ContainerRequestFilter. Use ContainerRequestContext.html#setSecurityContext with a UserPrincipal and inject the SecurityContext as ResourceMethod-Parameter.
  3. Implement a CDI-Interceptor which updates your Method-Parameters.
  4. Implement a class which produces your user and inject it via CDI.

I pushed examples to github.

查看更多
Juvenile、少年°
3楼-- · 2019-01-23 20:12

While lefloh's answer is definitely correct I'd like to elaborate on his 2nd approach which I find most convenient.

You're to create Interceptor(Filter) which implements ContainerRequestFilter.

import org.apache.commons.lang.StringUtils;
import org.jboss.resteasy.spi.ResteasyProviderFactory;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.io.IOException;

@Provider
public class UserProvider implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext containerRequestContext) throws IOException {
        String userId = containerRequestContext.getHeaders().getFirst("User-Id");
        if (StringUtils.isEmpty(userId)) {
            Response response = Response
                    .status(Response.Status.BAD_REQUEST)
                    .type(MediaType.TEXT_PLAIN_TYPE)
                    .entity("User-Id header is missing.")
                    .build();
            containerRequestContext.abortWith(response);
            return;
        }

        //do your logic to obtain the User object by userId

        ResteasyProviderFactory.pushContext(User.class, user);
    }
}

You'd need to register it either by autodiscovering set up in web.xml

<context-param>
    <param-name>resteasy.scan</param-name>
    <param-value>true</param-value>
</context-param>

or in your app boot

ResteasyProviderFactory.getInstance().registerProvider(UserProvider.class);

Since all the logic behind fetching the user is in the UserProvider you can for example use it like this

@Path("/orders")
@GET
public List<Order> getOrders(@Context User user) {
    return user.getOrders();
}
查看更多
登录 后发表回答