Short entry this time to explain another bug I found during the past weeks in jersey (jax-rs implementation for glassfish and other javaee containers). This time is a very specific problem but it stopped me for a long time because I did not check in what conditions it was exposed.
I needed to develop a little Java program that executed some operations against a REST server. This server was protected using https and BASIC authentication. But there was a problem, the certificate was the default one and it did not correspond with the hostname of the machine. I was not worried because there are ways to bypass default checks in Java (see this wonderful entry by Nakov) that mainly override default hostname verifier and default SSL certificate checker. But after adapting the solution in the jersey client, it did not work. I was in a hurry so I decided to bypass the problem someway and check it later. Finally today I have the time to review the problem and now I have a clear idea about what is happening.
The problem is the combination of setting BASIC authentication (non-preemptive) and custom SslContext and HostnameVerifier. In this combination the jersey client performs the call, and, if the HTTP error 401 is returned (unauthorized) it executes a second call sending the proper authentication header. The problem is that a new client is used in the latter operation and it is not initialized with the same context and verifiers.
Here it is a simple main program that exemplifies the problem:
static public void main(String[] args) throws Exception {
SSLContext sc = SSLContext.getInstance("SSL");
TrustManager[] trustAllCerts = { new InsecureTrustManager() };
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HostnameVerifier allHostsValid = new InsecureHostnameVerifier();
Client client = ClientBuilder.newBuilder().sslContext(sc)
.hostnameVerifier(allHostsValid).build();
client.register(HttpAuthenticationFeature.basicBuilder()
// comment this line or not to check the bug
.nonPreemptive()
.credentials("ricky", "XXXXX").build());
Response response = client.target("https://localhost:8181/jaxrs-sample")
.path("webresources")
.path("hellows")
.path("sayhello")
.queryParam("name", "ricky")
.request()
.get();
if (response.getStatusInfo().getFamily().equals(
Response.Status.Family.SUCCESSFUL)) {
System.out.println(response.readEntity(String.class));
} else {
throw new Exception(
String.format("HTTP error (%d): %s", response.getStatus(),
response.getStatusInfo().getReasonPhrase()));
}
}
I revised the source code and the problem was easy to fix, the new client should be created assigning both elements too (HostnameVerifier and SslContext of the original client). I created the following straight fix:
--- org/glassfish/jersey/client/authentication/HttpAuthenticationFilter.java.ORIG 2014-03-01 17:17:11.000000000 +0100
+++ org/glassfish/jersey/client/authentication/HttpAuthenticationFilter.java 2014-03-01 17:16:40.000000000 +0100
@@ -296,7 +296,11 @@
/
static boolean repeatRequest(ClientRequestContext request, ClientResponseContext response, String newAuthorizationHeader) {
- Client client = ClientBuilder.newClient(request.getConfiguration());
+ Client client = ClientBuilder.newBuilder()
+ .hostnameVerifier(request.getClient().getHostnameVerifier())
+ .sslContext(request.getClient().getSslContext())
+ .withConfig(request.getConfiguration())
+ .build();
String method = request.getMethod();
MediaType mediaType = request.getMediaType();
URI lUri = request.getUri();
Finally a bug against jersey project was opened. In the meantime my script avoids non-preemptive authentication which is actually a better configuration in my case (the second call is avoided). The problem was that I copied the basic setup from an example and I did not check what the hell was the nonPreemptive method. Too many bugs in the last months.
Cheerio!
Comments