10
votes

I've written a simple REST server using JAX-RS, Jersey and Grizzly. This is how I start the server:

URI baseUri = UriBuilder.fromUri("http://localhost/api")
                        .port(8081)
                        .build();

ResourceConfig rc = new PackagesResourceConfig("se.aioobe.resources");
HttpServer httpServer = GrizzlyServerFactory.createHttpServer(baseUri, rc);

Now I need to protect the resources using Basic HTTP authentication, and I can't figure out how to do this.

I can switch from Grizzly to for instance Jetty if it is simpler to get it to work, but I really value the simple configuration / start up that Grizzly provides.

I've read a lot of tutorials. They all mention the web.xml but in my current configuration I don't have one. (Do I need to add one for HTTP authentication?) I've found the following questions, neither of them is of any help :-(

(No SSL required at this point. The authentication is at this point just to prevent the public from peeking at our beta.)

TL;DR: How do I add basic HTTP authentication to a Jersey / Grizzly webapp?

3
You want to have on the container level, or JAX-RS level will also work?yegor256
so how you solve this issue? Because I have the same problem.Java Dude
If there's an accepted answer to a question, you can safely assume that that was the way the OP solved the problem. In this case I even posted the answer my self.aioobe

3 Answers

12
votes

I managed to get it working after a couple of hours, based on this blog post.

My solution involves:

  • Maven artifacts:
    • jersey-server (v 1.17)
    • jersey-grizzly2 (v 1.17)
  • Hard coded username / password (replace with database lookup if you like)
  • No web.xml (programatically configured server)
  • No SSL involved

I created this ContainerRequestFilter:

public class AuthFilter implements ContainerRequestFilter {

    // Exception thrown if user is unauthorized.
    private final static WebApplicationException unauthorized =
       new WebApplicationException(
           Response.status(Status.UNAUTHORIZED)
                   .header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"realm\"")
                   .entity("Page requires login.").build());

    @Override
    public ContainerRequest filter(ContainerRequest containerRequest) 
            throws WebApplicationException {

        // Automatically allow certain requests.
        String method = containerRequest.getMethod();
        String path = containerRequest.getPath(true);
        if (method.equals("GET") && path.equals("application.wadl"))
            return containerRequest;

        // Get the authentication passed in HTTP headers parameters
        String auth = containerRequest.getHeaderValue("authorization");
        if (auth == null)
            throw unauthorized;

        auth = auth.replaceFirst("[Bb]asic ", "");
        String userColonPass = Base64.base64Decode(auth);

        if (!userColonPass.equals("admin:toHah1ooMeor6Oht"))
            throw unauthorized;

        return containerRequest;
    }
}

And I then changed the startup code to include the filter:

URI baseUri = UriBuilder.fromUri("http://localhost/api")
                        .port(8081)
                        .build();

ResourceConfig rc = new PackagesResourceConfig("se.aioobe.resources");

// Add AuthFilter ////////////
rc.getProperties().put("com.sun.jersey.spi.container.ContainerRequestFilters",
                       "<YOUR PACKAGE FOR AuthFilter>.AuthFilter");
//////////////////////////////

HttpServer httpServer = GrizzlyServerFactory.createHttpServer(baseUri, rc);
4
votes

You may want to check HTTPS Client Server Grizzly sample distributed with Jersey which exactly does that. Here is a gist from that sample on configuring security filters on the Grizzly server.

    WebappContext context = new WebappContext("context");
    ServletRegistration registration = 
            context.addServlet("ServletContainer", ServletContainer.class);
    registration.setInitParameter("com.sun.jersey.config.property.packages",
            "com.sun.jersey.samples.https_grizzly.resource;com.sun.jersey.samples.https_grizzly.auth");

    // add security filter (which handles http basic authentication)
    registration.setInitParameter(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
            "com.sun.jersey.samples.https_grizzly.auth.SecurityFilter;com.sun.jersey.api.container.filter.LoggingFilter");
    registration.setInitParameter(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
            LoggingFilter.class.getName());


    try {

        webServer = GrizzlyServerFactory.createHttpServer(
                getBaseURI()
        );

        // start Grizzly embedded server //
        System.out.println("Jersey app started. Try out " + BASE_URI + "\nHit CTRL + C to stop it...");
        context.deploy(webServer);
        webServer.start();

    } catch (Exception ex) {
        System.out.println(ex.getMessage());
    }

You are registering a SecurityFilter which takes care of your HTTP basic auth. Enabling logging filter on the server should show basic auth header in the request. Here is an example:

Jan 30, 2013 8:59:00 PM com.sun.jersey.api.container.filter.LoggingFilter filter
INFO: 1 * Server in-bound request
1 > GET http://localhost:8080/context/
1 > host: localhost:8080
1 > connection: keep-alive
1 > cache-control: max-age=0
1 > authorization: Basic dXNlcjpwYXNzd29yZA==
1 > accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
1 > user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17
1 > accept-encoding: gzip,deflate,sdch
1 > accept-language: en-US,en;q=0.8
1 > accept-charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
1 > 

Service: GET / User: user
Jan 30, 2013 8:59:00 PM com.sun.jersey.api.container.filter.LoggingFilter$Adapter finish
INFO: 1 * Server out-bound response
1 < 200
1 < Content-Type: text/html
1 < 
JERSEY HTTPS EXAMPLE
1
votes

@aioobe be aware that although this will kind-of work you need better error checking when you're working with the header. For example:

    auth = auth.replaceFirst("[Bb]asic ", "");

This assumes that the authentication header is Basic, whereas it might not be. You should check that the authorization header starts with 'Basic' and if not throw unauthorized. Same thing for ensuring that the rest of the information is actually base64-encoded.