Issue Details (XML | Word | Printable)

Key: JERSEY-2061
Type: Bug Bug
Status: Resolved Resolved
Resolution: Won't Fix
Priority: Major Major
Assignee: Unassigned
Reporter: gdavison
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
jersey

Client can be forced to connect to a resource with a different set of credentials if previously accessed via HttpURLConnection

Created: 22/Aug/13 02:36 PM   Updated: 14/Jan/14 10:18 AM   Resolved: 14/Jan/14 10:18 AM
Component/s: security
Affects Version/s: 1.17, 2.4.1
Fix Version/s: 2.6

Time Tracking:
Not Specified

File Attachments: 1. Java Source File AuthenticatorClientTest.java (5 kB) 05/Dec/13 05:16 PM - Michal Gajdos

Environment:

JDK 7u17, Linux


Tags: client security
Participants: gdavison, Marek Potociar and Michal Gajdos


 Description  « Hide

The Java authenticator framework use by URL, HttpURLConnection and the like for user interface reasons will remember the credentials used to access a particular URI over the life of the VM until such time as this set of credentials return a 401 response.

Unfortunately this can mean that the default handler for the Client will inherit this pre-cached information. In this example the expected output should be:

username="authenticator",
username="jersey",

but instead when you run this code you see:

username="authenticator",
username="authenticator",

Here is the code that exercises the bug, if you modify the code to use Basic rather than Digest then it will pass; but that is because of JERSEY-2050.

package authenticatorissue;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPDigestAuthFilter;
import com.sun.jersey.api.container.httpserver.HttpServerFactory;
import com.sun.jersey.api.core.ClassNamesResourceConfig;
import com.sun.net.httpserver.HttpServer;

import java.io.DataInputStream;

import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;

import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

public class VerifyIssue {
    public static void main(String[] args) throws Exception {

        // Publish the REST endpoint

        HttpServer server =
            HttpServerFactory.create("http://localhost:8080/rest",
                                     new ClassNamesResourceConfig(GetResourceBasic.class));

        try {
            server.start();

            // Replace the VM default authenticator with one that just returns authenticator/authenticator

            Authenticator.setDefault(new Authenticator() {

                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication("authenticator", "authenticator".toCharArray());
                }
            });

            // Get that resource using just URL

            URL resource = new URL("http://localhost:8080/rest/get-basic");
            try (DataInputStream dis = new DataInputStream(resource.openStream())) {

                byte buffer[] = new byte[1024];
                int length = dis.read(buffer);
                System.out.println(new String(buffer, 0, length));

            }

            // Now get that resourece using a Jersey client

            Client client = Client.create();
            client.addFilter(new HTTPDigestAuthFilter("jersey", "jersey"));

            WebResource webResource = client.resource(resource.toURI());

            System.out.println(webResource.get(String.class));


            //

        } finally {
            server.stop(0);

        }

    }


    @Path("/get-basic")
    public static class GetResourceBasic {

        @GET
        public Response get(@HeaderParam("authorization") String authenticate) {

            if (authenticate != null) {
                // Dump out the user name
                return Response.ok(authenticate.split(" ")[1].split(", ")[0]).build();

            } else {
                return Response.status(Response.Status.UNAUTHORIZED).header("WWW-Authenticate",
                                                                            "Digest realm=\"example\", qop=\"auth,auth-int\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"").build();
            }
        }
    }

}


Michal Gajdos added a comment - 05/Dec/13 05:14 PM

Targeting for 2.6 although I don't thing this has a solution. When authentication mechanism is being negotiated (401 from server + WWW-Authenticate header) the HttpUrlConnection will send authorization header itself (since it has an instance of Authenticator set) without delegating into digest filter. Based on the code ([1]) I don't see a way how to override it.

[1] http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/sun/net/www/protocol/http/HttpURLConnection.java


Michal Gajdos added a comment - 05/Dec/13 05:16 PM

Test-Case attached.


Marek Potociar added a comment - 14/Jan/14 10:18 AM

There does not seem to be a viable solution for the issue. Closing as won't fix.