Skip to content

Commit 3996617

Browse files
authored
Fix custom SSLSocketFactory not being set because of an unsafe lazy-initialization in JDK (#4566)
* Avoid unsafe lazy-initialization for SSL sockets This prevents calling HttpsURLConnection.getDefaultSSLSocketFactory() in an unsafe manner due to the poorly implemented lazy-initialization on the JDK. When multiple threads call that method concurrently (calling secureConnection()) the SSLSocketFactory is instantiated two times, making one thread fail the check and overriding the custom socket factory with the default one. Also-by: Kevin Conaway <[email protected]> Signed-off-by: Adrian Haasler García <[email protected]>
1 parent d264efa commit 3996617

File tree

2 files changed

+67
-1
lines changed

2 files changed

+67
-1
lines changed

core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ public class HttpUrlConnector implements Connector {
7171

7272
private static final Logger LOGGER = Logger.getLogger(HttpUrlConnector.class.getName());
7373
private static final String ALLOW_RESTRICTED_HEADERS_SYSTEM_PROPERTY = "sun.net.http.allowRestrictedHeaders";
74+
// Avoid multi-thread uses of HttpsURLConnection.getDefaultSSLSocketFactory() because it does not implement a
75+
// proper lazy-initialization. See https://github.com/jersey/jersey/issues/3293
76+
private static final SSLSocketFactory DEFAULT_SSL_SOCKET_FACTORY = HttpsURLConnection.getDefaultSSLSocketFactory();
7477
// The list of restricted headers is extracted from sun.net.www.protocol.http.HttpURLConnection
7578
private static final String[] restrictedHeaders = {
7679
"Access-Control-Request-Headers",
@@ -302,7 +305,7 @@ protected void secureConnection(final JerseyClient client, final HttpURLConnecti
302305
suc.setHostnameVerifier(verifier);
303306
}
304307

305-
if (HttpsURLConnection.getDefaultSSLSocketFactory() == suc.getSSLSocketFactory()) {
308+
if (DEFAULT_SSL_SOCKET_FACTORY == suc.getSSLSocketFactory()) {
306309
// indicates that the custom socket factory was not set
307310
suc.setSSLSocketFactory(sslSocketFactory.get());
308311
}

tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/ssl/SslHttpUrlConnectorTest.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,18 @@
1717
package org.glassfish.jersey.tests.e2e.client.connector.ssl;
1818

1919
import java.io.IOException;
20+
import java.io.PrintWriter;
21+
import java.io.StringWriter;
2022
import java.net.HttpURLConnection;
2123
import java.net.InetAddress;
2224
import java.net.Socket;
2325
import java.net.URL;
26+
import java.util.List;
27+
import java.util.concurrent.CopyOnWriteArrayList;
28+
import java.util.concurrent.CyclicBarrier;
29+
import java.util.concurrent.ExecutorService;
30+
import java.util.concurrent.Executors;
31+
import java.util.concurrent.TimeUnit;
2432

2533
import javax.ws.rs.client.Client;
2634
import javax.ws.rs.client.ClientBuilder;
@@ -79,6 +87,61 @@ public HttpURLConnection getConnection(final URL url) throws IOException {
7987
assertTrue(socketFactory.isVisited());
8088
}
8189

90+
/**
91+
* Test for https://github.com/jersey/jersey/issues/3293
92+
*
93+
* @author Kevin Conaway
94+
*/
95+
@Test
96+
public void testConcurrentRequestsWithCustomSSLContext() throws Exception {
97+
final SSLContext sslContext = getSslContext();
98+
99+
final Client client = ClientBuilder.newBuilder()
100+
.sslContext(sslContext)
101+
.register(HttpAuthenticationFeature.basic("user", "password"))
102+
.register(LoggingFeature.class)
103+
.build();
104+
105+
int numThreads = 5;
106+
CyclicBarrier barrier = new CyclicBarrier(numThreads);
107+
ExecutorService service = Executors.newFixedThreadPool(numThreads);
108+
List<Exception> exceptions = new CopyOnWriteArrayList<>();
109+
110+
for (int i = 0; i < numThreads; i++) {
111+
service.submit(() -> {
112+
try {
113+
barrier.await(1, TimeUnit.MINUTES);
114+
for (int call = 0; call < 10; call++) {
115+
final Response response = client.target(Server.BASE_URI).path("/").request().get();
116+
assertEquals(200, response.getStatus());
117+
}
118+
} catch (Exception ex) {
119+
exceptions.add(ex);
120+
}
121+
});
122+
}
123+
124+
service.shutdown();
125+
126+
assertTrue(
127+
service.awaitTermination(1, TimeUnit.MINUTES)
128+
);
129+
130+
assertTrue(
131+
toString(exceptions),
132+
exceptions.isEmpty()
133+
);
134+
}
135+
136+
private String toString(List<Exception> exceptions) {
137+
StringWriter writer = new StringWriter();
138+
PrintWriter printWriter = new PrintWriter(writer);
139+
140+
exceptions.forEach(e -> e.printStackTrace(printWriter));
141+
142+
return writer.toString();
143+
}
144+
82145
public static class CustomSSLSocketFactory extends SSLSocketFactory {
83146

84147
private boolean visited = false;

0 commit comments

Comments
 (0)