Skip to content
This repository was archived by the owner on May 28, 2018. It is now read-only.

Commit 30ed007

Browse files
author
alessandro.gherardi
committed
Patch for client connection leak when using digest authentication
1 parent 7d4c8b0 commit 30ed007

File tree

3 files changed

+55
-6
lines changed

3 files changed

+55
-6
lines changed

connectors/apache-connector/src/test/java/org/glassfish/jersey/apache/connector/AuthTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.apache.http.auth.AuthScope;
6565
import org.apache.http.auth.UsernamePasswordCredentials;
6666
import org.apache.http.client.CredentialsProvider;
67+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
6768
import org.junit.Ignore;
6869
import org.junit.Test;
6970
import static org.junit.Assert.assertEquals;
@@ -168,6 +169,19 @@ public String getFilter(@Context HttpHeaders h) {
168169
return "GET";
169170
}
170171

172+
@GET
173+
@Path("digest")
174+
public String getDigest(@Context HttpHeaders h) {
175+
String value = h.getRequestHeaders().getFirst("Authorization");
176+
if (value == null) {
177+
throw new WebApplicationException(
178+
Response.status(401).header("WWW-Authenticate", "Digest realm=\"WallyWorld\"")
179+
.entity("Forbidden").build());
180+
}
181+
182+
return "GET";
183+
}
184+
171185
@POST
172186
public String post(@Context HttpHeaders h, String e) {
173187
requestCount++;
@@ -259,6 +273,24 @@ public void testAuthGetWithClientFilter() {
259273
assertEquals("GET", r.request().get(String.class));
260274
}
261275

276+
@Test
277+
public void testAuthGetWithDigestFilter() {
278+
ClientConfig cc = new ClientConfig();
279+
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
280+
cc.connectorProvider(new ApacheConnectorProvider());
281+
cc.property(ApacheClientProperties.CONNECTION_MANAGER, cm);
282+
Client client = ClientBuilder.newClient(cc);
283+
client.register(HttpAuthenticationFeature.universal("name", "password"));
284+
WebTarget r = client.target(getBaseUri()).path("test/digest");
285+
286+
assertEquals("GET", r.request().get(String.class));
287+
288+
// Verify the connection that was used for the request is available for reuse
289+
// and no connections are leased
290+
assertEquals(cm.getTotalStats().getAvailable(), 1);
291+
assertEquals(cm.getTotalStats().getLeased(), 0);
292+
}
293+
262294
@Test
263295
@Ignore("JERSEY-1750: Cannot retry request with a non-repeatable request entity. How to buffer the entity?"
264296
+ " Allow repeatable write in jersey?")

core-client/src/main/java/org/glassfish/jersey/client/authentication/BasicAuthenticator.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040

4141
package org.glassfish.jersey.client.authentication;
4242

43+
import java.io.IOException;
44+
4345
import javax.ws.rs.client.ClientRequestContext;
4446
import javax.ws.rs.client.ClientResponseContext;
4547
import javax.ws.rs.core.HttpHeaders;
@@ -113,7 +115,8 @@ public void filterRequest(ClientRequestContext request) throws RequestAuthentica
113115
* new request was done with digest authentication information and authentication was successful.
114116
* @throws ResponseAuthenticationException in case that basic credentials missing or are in invalid format
115117
*/
116-
public boolean filterResponseAndAuthenticate(ClientRequestContext request, ClientResponseContext response) {
118+
public boolean filterResponseAndAuthenticate(ClientRequestContext request, ClientResponseContext response)
119+
throws IOException {
117120
final String authenticate = response.getHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE);
118121
if (authenticate != null && authenticate.trim().toUpperCase().startsWith("BASIC")) {
119122
HttpAuthenticationFilter.Credentials credentials = HttpAuthenticationFilter

core-client/src/main/java/org/glassfish/jersey/client/authentication/HttpAuthenticationFilter.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@
4949
import java.util.List;
5050
import java.util.Map;
5151

52+
import javax.annotation.Priority;
5253
import javax.ws.rs.Priorities;
5354
import javax.ws.rs.client.Client;
54-
import javax.ws.rs.client.ClientBuilder;
5555
import javax.ws.rs.client.ClientRequestContext;
5656
import javax.ws.rs.client.ClientRequestFilter;
5757
import javax.ws.rs.client.ClientResponseContext;
@@ -66,8 +66,6 @@
6666
import javax.ws.rs.core.MultivaluedMap;
6767
import javax.ws.rs.core.Response;
6868

69-
import javax.annotation.Priority;
70-
7169
import org.glassfish.jersey.client.ClientProperties;
7270
import org.glassfish.jersey.client.internal.LocalizationMessages;
7371

@@ -292,10 +290,20 @@ private void updateCache(ClientRequestContext request, boolean success, Type ope
292290
* @param newAuthorizationHeader {@code Authorization} header that should be added to the new request.
293291
* @return {@code true} is the authentication was successful ({@code true} if 401 response code was not returned;
294292
* {@code false} otherwise).
293+
* @throws IOException
295294
*/
296-
static boolean repeatRequest(ClientRequestContext request, ClientResponseContext response, String newAuthorizationHeader) {
295+
static boolean repeatRequest(ClientRequestContext request, ClientResponseContext response, String newAuthorizationHeader)
296+
throws IOException {
297+
// If the failed response has an entity stream, close it. We must do this to avoid leaking a connection
298+
// when we replace the entity stream of the failed response with that of the repeated response (see below).
299+
// Notice that by closing the entity stream before sending the repeated request we allow the connection allocated
300+
// to the failed request to be reused, if possible, for the repeated request.
301+
if (response.hasEntity()) {
302+
response.getEntityStream().close();
303+
response.setEntityStream(null);
304+
}
305+
297306
Client client = request.getClient();
298-
299307
String method = request.getMethod();
300308
MediaType mediaType = request.getMediaType();
301309
URI lUri = request.getUri();
@@ -318,6 +326,12 @@ static boolean repeatRequest(ClientRequestContext request, ClientResponseContext
318326

319327
builder.property(REQUEST_PROPERTY_FILTER_REUSED, "true");
320328

329+
// Copy other properties, if any, from the original request
330+
for (String propertyName : request.getPropertyNames()) {
331+
Object propertyValue = request.getProperty(propertyName);
332+
builder.property(propertyName, propertyValue);
333+
}
334+
321335
Invocation invocation;
322336
if (request.getEntity() == null) {
323337
invocation = builder.build(method);

0 commit comments

Comments
 (0)