6

I have a web service like this:

@Path("/projects")
public class Projects {
    [...]

    @Inject
    CurrentRequest current;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("{id}")
    public Response getProject(@PathParam("id") String id) {
        if (current.isUserAuthenticated()) {
            [...do something...]
        } else {
            [...produce an error...]
        }
    }
}

And a CDI bean with an auth checker method like this:

@RequestScoped
public class CurrentRequest {

    public boolean isUserAuthenticated() {
        [...do some header checking...]
    }
}

My problem is that I can't for the life of me get hold of the HTTP headers from inside CurrentRequest. I tried injecting HttpServletRequest, but it's not initialized. I tried using @Context, same thing. Obviously FacesContext.getCurrentInstance() doesn't work either because there is no FacesContext.

I see that this question is basically asking the same thing, but hasn't received much attention.

My current approach is to use @Context HttpServletRequest request inside Projects and pass it as a parameter to current.isUserAuthenticated(request). But that feels so wrong. Shouldn't the CDI bean know its own request?

What am I missing?

Community
  • 1
  • 1
Antares42
  • 1,406
  • 1
  • 15
  • 45

2 Answers2

6

Extracting the HTTP headers

You don't need the HttpServletRequest in your JAX-RS endpoints to get the HTTP headers from the request. Instead, you can inject HttpHeaders:

@Context
HttpHeaders httpHeaders;

Then you can use the HttpHeaders API to get the header values:

If you need the value of a standard HTTP header, consider using the constants available in the HttpHeaders API:

// Get the value of the Authorization header
String authorizationHeader = httpHeaders.getHeaderString(HttpHeaders.AUTHORIZATION);

Using filters

Since you are performing authentication and/or authorization, I would recommend using filters, so you can keep your REST endpoints lean and focused on the business logic.

To bind filters to your REST endpoints, JAX-RS provides the meta-annotation @NameBinding and can be used as following:

@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured { }

The @Secured annotation will be used to decorate a filter class, which implements ContainerRequestFilter, allowing you to handle the request.

The ContainerRequestContext helps you to extract information from the HTTP request (for more details, have a look at the ContainerRequestContext API):

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

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        // Use the ContainerRequestContext to extract information from the HTTP request
        // Information such as the URI, headers and HTTP entity are available
    }
}

The ContainerRequestFilter#filter() method is a good place to abort the request if the user is not authenticated/authorized. To do it, you can use ContainerRequestContext#abortWith() or throw an exception.

The @Provider annotation marks an implementation of an extension interface that should be discoverable by JAX-RS runtime during a provider scanning phase.

To bind the filter to your endpoints methods or classes, annotate them with the @Secured annotation created above. For the methods and/or classes which are annotated, the filter will be executed.

@Path("/")
public class MyEndpoint {

    @GET
    @Path("{id}")
    @Produces("application/json")
    public Response myUnsecuredMethod(@PathParam("id") Long id) {
        // This method is not annotated with @Secured
        // The security filter won't be executed before invoking this method
        ...
    }

    @DELETE
    @Secured
    @Path("{id}")
    @Produces("application/json")
    public Response mySecuredMethod(@PathParam("id") Long id) {
        // This method is annotated with @Secured
        // The security filter will be executed before invoking this method
        ...
    }
}

In the example above, the security filter will be executed only for mySecuredMethod(Long) because it's annotated with @Secured.

You can have as many filters as you need for your REST endpoints. To ensure the execution order of the filters, annotate them with @Priority.

It's highly recommended to use one of the values defined in the Priorities class (the following order will be used):

If your filter is not annotated with @Priority, the filter will be executed with the USER priority.

Additional information

You'll probably find this answer useful.

cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • I think you misunderstood me with the first part of your response - I _can_ get the headers in the JAX-RS class, just not directly in the CDI bean, hence my current approach of passing the HttpServletRequest to the bean. But I do like your idea of using Name Binding and annotations instead of an injected bean. I'll give it a go tomorrow and report back! – Antares42 Oct 07 '15 at 19:53
  • Haven't had time yet, sorry. – Antares42 Oct 16 '15 at 08:58
  • This answer did it for me ! Thanks. – MMacphail Oct 11 '17 at 06:27
  • 1
    And I am here waiting for @Antares42 to let us know if it was possible to inject the context directly on the EJB – Ordiel Jul 14 '22 at 23:25
  • In my case adding `@Context` was enough, it just take me some time since I was importing the wrong `HttpHeaders` class – Ordiel Jul 15 '22 at 19:34
  • @Ordiel - Back in EE6 I used a servlet filter to set up a user bean representing the client. Then after migrating to EE7 I changed that into a ContainerRequestFilter at the PreMatching stage. So I never got back to the original idea of accessing the HTTP headers from the bean. – Antares42 Sep 02 '22 at 12:17
1

Depending on the JAX-RS implementation and the server you are using, you might need some dependencies to provide integration between CDI and JAX-RS:

Jersey Ext Cdi1x

<dependency>
    <groupId>org.glassfish.jersey.ext.cdi</groupId>
    <artifactId>jersey-cdi1x</artifactId>
    <version>2.22.1</version>
</dependency>

RESTEasy CDI Integration Module

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-cdi</artifactId>
    <version>3.0.13.Final</version>
</dependency>

Apache CXF CDI Integration

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-integration-cdi</artifactId>
    <version>3.1.3</version>
</dependency>
cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • The project currently runs on EE6 with RESTEasy 3.0.4 and is deployed on an EAP 6.3. I also read here on SO that the injection should work out of the box in EE7, so I tried upgrading, but still got WELD errors on deploying. I'll try these integrators when I have time. – Antares42 Oct 16 '15 at 09:45
  • @Antares42 The Weld stacktrace will be useful. The injection of `HttpServletRequest` is supported out of the box in Java EE 7 (CDI 1.1). In Java EE 6 (CDI 1.0) you don't have this feature out of the box. What you can do is create a `ServletRequestListener`, store the `HttpServletRequest` in a `ThreadLocal` and create a CDI producer method for the `HttpServletRequest`. – cassiomolin Oct 16 '15 at 10:27