Skip to content

Commit

Permalink
Allowing using SSLContext supplier per request by the NettyConnector
Browse files Browse the repository at this point in the history
Signed-off-by: jansupol <[email protected]>
  • Loading branch information
jansupol committed Sep 7, 2023
1 parent 5a2b251 commit 73bdf5f
Show file tree
Hide file tree
Showing 13 changed files with 566 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -72,7 +72,7 @@ public class NettyClientProperties {
/**
* The maximal number of redirects during single request.
* <p/>
* Value is expected to be positive {@link Integer}. Default value is {@value #DEFAULT_MAX_REDIRECTS}.
* Value is expected to be positive {@link Integer}. Default value is 5.
* <p/>
* HTTP redirection must be enabled by property {@link org.glassfish.jersey.client.ClientProperties#FOLLOW_REDIRECTS},
* otherwise {@code MAX_REDIRECTS} is not applied.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import javax.net.ssl.SSLContext;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Configuration;
Expand Down Expand Up @@ -197,8 +199,10 @@ protected void execute(final ClientRequest jerseyRequest, final Set<URI> redirec
int port = requestUri.getPort() != -1 ? requestUri.getPort() : "https".equals(requestUri.getScheme()) ? 443 : 80;

try {
final SSLParamConfigurator sslConfig = SSLParamConfigurator.builder()
.request(jerseyRequest).setSNIAlways(true).build();

String key = requestUri.getScheme() + "://" + host + ":" + port;
String key = requestUri.getScheme() + "://" + sslConfig.getSNIHostName() + ":" + port;
ArrayList<Channel> conns;
synchronized (connections) {
conns = connections.get(key);
Expand Down Expand Up @@ -228,9 +232,8 @@ protected void execute(final ClientRequest jerseyRequest, final Set<URI> redirec
}
}

Integer connectTimeout = jerseyRequest.resolveProperty(ClientProperties.CONNECT_TIMEOUT, 0);

if (chan == null) {
Integer connectTimeout = jerseyRequest.resolveProperty(ClientProperties.CONNECT_TIMEOUT, 0);
Bootstrap b = new Bootstrap();

// http proxy
Expand Down Expand Up @@ -267,7 +270,7 @@ protected void initChannel(SocketChannel ch) throws Exception {
if ("https".equals(requestUri.getScheme())) {
// making client authentication optional for now; it could be extracted to configurable property
JdkSslContext jdkSslContext = new JdkSslContext(
client.getSslContext(),
getSslContext(client, jerseyRequest),
true,
(Iterable) null,
IdentityCipherSuiteFilter.INSTANCE,
Expand All @@ -278,8 +281,7 @@ protected void initChannel(SocketChannel ch) throws Exception {
);

final int port = requestUri.getPort();
final SSLParamConfigurator sslConfig = SSLParamConfigurator.builder()
.request(jerseyRequest).setSNIAlways(true).build();

final SslHandler sslHandler = jdkSslContext.newHandler(
ch.alloc(), sslConfig.getSNIHostName(), port <= 0 ? 443 : port, executorService
);
Expand Down Expand Up @@ -455,6 +457,11 @@ public void run() {
}
}

private SSLContext getSslContext(Client client, ClientRequest request) {
Supplier<SSLContext> supplier = request.resolveProperty(ClientProperties.SSL_CONTEXT_SUPPLIER, Supplier.class);
return supplier == null ? client.getSslContext() : supplier.get();
}

private String buildPathWithQueryParameters(URI requestUri) {
if (requestUri.getRawQuery() != null) {
return String.format("%s?%s", requestUri.getRawPath(), requestUri.getRawQuery());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.internal.util.PropertyAlias;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;

/**
Expand Down Expand Up @@ -481,6 +482,16 @@ public final class ClientProperties {
*/
public static final String CONNECTOR_PROVIDER = "jersey.config.client.connector.provider";

/**
* <p>The {@link javax.net.ssl.SSLContext} {@link java.util.function.Supplier} to be used to set ssl context in the current
* HTTP request. Has precedence over the {@link Client#getSslContext()}.
* </p>
* <p>Currently supported by the default {@code HttpUrlConnector} and by {@code NettyConnector} only.</p>
* @since 2.41
* @see org.glassfish.jersey.client.SslContextClientBuilder
*/
public static final String SSL_CONTEXT_SUPPLIER = "jersey.config.client.ssl.context.supplier";

private ClientProperties() {
// prevents instantiation
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand All @@ -20,13 +20,13 @@
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -40,9 +40,7 @@
import org.glassfish.jersey.SslConfigurator;
import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.client.spi.DefaultSslContextProvider;
import org.glassfish.jersey.internal.ServiceFinder;
import org.glassfish.jersey.internal.util.collection.UnsafeValue;
import org.glassfish.jersey.internal.util.collection.Values;

import static org.glassfish.jersey.internal.guava.Preconditions.checkNotNull;
import static org.glassfish.jersey.internal.guava.Preconditions.checkState;
Expand All @@ -67,7 +65,7 @@ public SSLContext getDefaultSslContext() {
private final boolean isDefaultSslContext;
private final ClientConfig config;
private final HostnameVerifier hostnameVerifier;
private final UnsafeValue<SSLContext, IllegalStateException> sslContext;
private final Supplier<SSLContext> sslContext;
private final LinkedBlockingDeque<WeakReference<JerseyClient.ShutdownHook>> shutdownHooks =
new LinkedBlockingDeque<WeakReference<JerseyClient.ShutdownHook>>();
private final ReferenceQueue<JerseyClient.ShutdownHook> shReferenceQueue = new ReferenceQueue<JerseyClient.ShutdownHook>();
Expand All @@ -86,7 +84,7 @@ interface ShutdownHook {
* Create a new Jersey client instance using a default configuration.
*/
protected JerseyClient() {
this(null, (UnsafeValue<SSLContext, IllegalStateException>) null, null, null);
this(null, new SslContextClientBuilder(), null, null);
}

/**
Expand Down Expand Up @@ -115,7 +113,9 @@ protected JerseyClient(final Configuration config,
final SSLContext sslContext,
final HostnameVerifier verifier,
final DefaultSslContextProvider defaultSslContextProvider) {
this(config, sslContext == null ? null : Values.unsafe(sslContext), verifier,
this(config,
sslContext == null ? new SslContextClientBuilder() : new SslContextClientBuilder().sslContext(sslContext),
verifier,
defaultSslContextProvider);
}

Expand Down Expand Up @@ -145,32 +145,32 @@ protected JerseyClient(final Configuration config,
final UnsafeValue<SSLContext, IllegalStateException> sslContextProvider,
final HostnameVerifier verifier,
final DefaultSslContextProvider defaultSslContextProvider) {
this.config = config == null ? new ClientConfig(this) : new ClientConfig(this, config);

if (sslContextProvider == null) {
this.isDefaultSslContext = true;

if (defaultSslContextProvider != null) {
this.sslContext = createLazySslContext(defaultSslContextProvider);
} else {
final DefaultSslContextProvider lookedUpSslContextProvider;

final Iterator<DefaultSslContextProvider> iterator =
ServiceFinder.find(DefaultSslContextProvider.class).iterator();

if (iterator.hasNext()) {
lookedUpSslContextProvider = iterator.next();
} else {
lookedUpSslContextProvider = DEFAULT_SSL_CONTEXT_PROVIDER;
}
this(config,
sslContextProvider == null
? new SslContextClientBuilder()
: new SslContextClientBuilder().sslContext(sslContextProvider.get()),
verifier,
defaultSslContextProvider
);
}

this.sslContext = createLazySslContext(lookedUpSslContextProvider);
}
} else {
this.isDefaultSslContext = false;
this.sslContext = Values.lazy(sslContextProvider);
/**
* Create a new Jersey client instance.
*
* @param config jersey client configuration.
* @param sslContextClientBuilder jersey client SSL context builder. The builder is expected to
* return non-default value.
* @param verifier jersey client host name verifier.
* @param defaultSslContextProvider default SSL context provider.
*/
JerseyClient(final Configuration config, final SslContextClientBuilder sslContextClientBuilder,
final HostnameVerifier verifier, final DefaultSslContextProvider defaultSslContextProvider) {
if (defaultSslContextProvider != null) {
sslContextClientBuilder.defaultSslContextProvider(defaultSslContextProvider);
}

this.config = config == null ? new ClientConfig(this) : new ClientConfig(this, config);
this.isDefaultSslContext = sslContextClientBuilder.isDefaultSslContext();
this.sslContext = sslContextClientBuilder;
this.hostnameVerifier = verifier;
}

Expand All @@ -195,15 +195,6 @@ private void release() {
}
}

private UnsafeValue<SSLContext, IllegalStateException> createLazySslContext(final DefaultSslContextProvider provider) {
return Values.lazy(new UnsafeValue<SSLContext, IllegalStateException>() {
@Override
public SSLContext get() {
return provider.getDefaultSslContext();
}
});
}

/**
* Register a new client shutdown hook.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,12 @@
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

import org.glassfish.jersey.SslConfigurator;
import org.glassfish.jersey.client.innate.inject.NonInjectionManager;
import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.client.spi.ClientBuilderListener;
import org.glassfish.jersey.client.spi.ConnectorProvider;
import org.glassfish.jersey.internal.ServiceFinder;
import org.glassfish.jersey.internal.config.ExternalPropertiesConfigurationFactory;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.collection.UnsafeValue;
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.model.internal.RankedComparator;
import org.glassfish.jersey.model.internal.RankedProvider;

Expand All @@ -54,8 +50,7 @@ public class JerseyClientBuilder extends ClientBuilder {

private final ClientConfig config;
private HostnameVerifier hostnameVerifier;
private SslConfigurator sslConfigurator;
private SSLContext sslContext;
private final SslContextClientBuilder sslContextClientBuilder = new SslContextClientBuilder();

private static final List<ClientBuilderListener> CLIENT_BUILDER_LISTENERS;

Expand Down Expand Up @@ -113,41 +108,19 @@ private static void init(ClientBuilder builder) {

@Override
public JerseyClientBuilder sslContext(SSLContext sslContext) {
if (sslContext == null) {
throw new NullPointerException(LocalizationMessages.NULL_SSL_CONTEXT());
}
this.sslContext = sslContext;
sslConfigurator = null;
sslContextClientBuilder.sslContext(sslContext);
return this;
}

@Override
public JerseyClientBuilder keyStore(KeyStore keyStore, char[] password) {
if (keyStore == null) {
throw new NullPointerException(LocalizationMessages.NULL_KEYSTORE());
}
if (password == null) {
throw new NullPointerException(LocalizationMessages.NULL_KEYSTORE_PASWORD());
}
if (sslConfigurator == null) {
sslConfigurator = SslConfigurator.newInstance();
}
sslConfigurator.keyStore(keyStore);
sslConfigurator.keyPassword(password);
sslContext = null;
sslContextClientBuilder.keyStore(keyStore, password);
return this;
}

@Override
public JerseyClientBuilder trustStore(KeyStore trustStore) {
if (trustStore == null) {
throw new NullPointerException(LocalizationMessages.NULL_TRUSTSTORE());
}
if (sslConfigurator == null) {
sslConfigurator = SslConfigurator.newInstance();
}
sslConfigurator.trustStore(trustStore);
sslContext = null;
sslContextClientBuilder.trustStore(trustStore);
return this;
}

Expand Down Expand Up @@ -194,22 +167,7 @@ public JerseyClient build() {
ExternalPropertiesConfigurationFactory.configure(this.config);
setConnectorFromProperties();

if (sslContext != null) {
return new JerseyClient(config, sslContext, hostnameVerifier, null);
} else if (sslConfigurator != null) {
final SslConfigurator sslConfiguratorCopy = sslConfigurator.copy();
return new JerseyClient(
config,
Values.lazy(new UnsafeValue<SSLContext, IllegalStateException>() {
@Override
public SSLContext get() {
return sslConfiguratorCopy.createSSLContext();
}
}),
hostnameVerifier);
} else {
return new JerseyClient(config, (UnsafeValue<SSLContext, IllegalStateException>) null, hostnameVerifier);
}
return new JerseyClient(config, sslContextClientBuilder, hostnameVerifier, null);
}

private void setConnectorFromProperties() {
Expand Down
Loading

0 comments on commit 73bdf5f

Please sign in to comment.