Skip to content

Commit 8cff800

Browse files
committed
Add configurable SSLSocketFactory support for HTTPS connections
1 parent 988951e commit 8cff800

5 files changed

Lines changed: 116 additions & 1 deletion

File tree

EXAMPLES.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ JwkProvider provider = new JwkProviderBuilder("https://samples.auth0.com/")
4444
.build();
4545
```
4646

47+
### Configure SSL/TLS settings
48+
49+
A custom `SSLSocketFactory` can be configured for HTTPS connections to the JWKS endpoint. This is useful for environments that require a specific TLS version, custom trust stores, or mutual TLS (mTLS).
50+
51+
```java
52+
// Configure a specific TLS version
53+
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
54+
sslContext.init(null, null, new SecureRandom());
55+
56+
JwkProvider provider = new JwkProviderBuilder("https://samples.auth0.com/")
57+
.sslSocketFactory(sslContext.getSocketFactory())
58+
.build();
59+
```
60+
61+
When not configured, the JVM's default SSL settings will be used.
62+
4763
See the [JwkProviderBuilder JavaDocs](https://javadoc.io/doc/com.auth0/jwks-rsa/latest/com/auth0/jwk/JwkProviderBuilder.html) for all available configurations.
4864

4965
## Error handling

src/main/java/com/auth0/jwk/JwkProviderBuilder.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.time.Duration;
66
import java.util.Map;
77
import java.util.concurrent.TimeUnit;
8+
import javax.net.ssl.SSLSocketFactory;
89

910
import static com.auth0.jwk.UrlJwkProvider.urlForDomain;
1011

@@ -24,6 +25,7 @@ public class JwkProviderBuilder {
2425
private BucketImpl bucket;
2526
private boolean rateLimited;
2627
private Map<String, String> headers;
28+
private SSLSocketFactory sslSocketFactory;
2729

2830
/**
2931
* Creates a new Builder with the given URL where to load the jwks from.
@@ -166,13 +168,26 @@ public JwkProviderBuilder headers(Map<String, String> headers) {
166168
return this;
167169
}
168170

171+
/**
172+
* Sets a custom {@link SSLSocketFactory} for HTTPS connections to the JWKS endpoint.
173+
* This allows configuration of TLS version, cipher suites, and custom certificate validation.
174+
* When not set, the JVM default SSL configuration will be used.
175+
*
176+
* @param sslSocketFactory the SSL socket factory to use for HTTPS connections (null for JVM default)
177+
* @return the builder
178+
*/
179+
public JwkProviderBuilder sslSocketFactory(SSLSocketFactory sslSocketFactory) {
180+
this.sslSocketFactory = sslSocketFactory;
181+
return this;
182+
}
183+
169184
/**
170185
* Creates a {@link JwkProvider}
171186
*
172187
* @return a newly created {@link JwkProvider}
173188
*/
174189
public JwkProvider build() {
175-
JwkProvider urlProvider = new UrlJwkProvider(url, connectTimeout, readTimeout, proxy, headers);
190+
JwkProvider urlProvider = new UrlJwkProvider(url, connectTimeout, readTimeout, proxy, headers, sslSocketFactory);
176191
if (this.rateLimited) {
177192
urlProvider = new RateLimitedJwkProvider(urlProvider, bucket);
178193
}

src/main/java/com/auth0/jwk/UrlJwkProvider.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import java.net.*;
99
import java.util.*;
1010
import java.util.concurrent.atomic.AtomicReference;
11+
import javax.net.ssl.HttpsURLConnection;
12+
import javax.net.ssl.SSLSocketFactory;
1113

1214
/**
1315
* Jwk provider that loads them from a {@link URL}
@@ -25,6 +27,7 @@ public class UrlJwkProvider implements JwkProvider {
2527
final Map<String, String> headers;
2628
final Integer connectTimeout;
2729
final Integer readTimeout;
30+
final SSLSocketFactory sslSocketFactory;
2831

2932
private final ObjectReader reader;
3033

@@ -59,6 +62,20 @@ public UrlJwkProvider(URL url, Integer connectTimeout, Integer readTimeout, Prox
5962
* @param headers a map of request header keys to values to send on the request. Default is "Accept: application/json".
6063
*/
6164
public UrlJwkProvider(URL url, Integer connectTimeout, Integer readTimeout, Proxy proxy, Map<String, String> headers) {
65+
this(url, connectTimeout, readTimeout, proxy, headers, null);
66+
}
67+
68+
/**
69+
* Creates a provider that loads from the given URL using custom request headers and SSL configuration.
70+
*
71+
* @param url to load the jwks
72+
* @param connectTimeout connection timeout in milliseconds (default is null)
73+
* @param readTimeout read timeout in milliseconds (default is null)
74+
* @param proxy proxy server to use when making the connection (default is null)
75+
* @param headers a map of request header keys to values to send on the request. Default is "Accept: application/json".
76+
* @param sslSocketFactory the SSL socket factory to use for HTTPS connections (null for JVM default)
77+
*/
78+
public UrlJwkProvider(URL url, Integer connectTimeout, Integer readTimeout, Proxy proxy, Map<String, String> headers, SSLSocketFactory sslSocketFactory) {
6279
Util.checkArgument(url != null, "A non-null url is required");
6380
Util.checkArgument(connectTimeout == null || connectTimeout >= 0, "Invalid connect timeout value '" + connectTimeout + "'. Must be a non-negative integer.");
6481
Util.checkArgument(readTimeout == null || readTimeout >= 0, "Invalid read timeout value '" + readTimeout + "'. Must be a non-negative integer.");
@@ -67,6 +84,7 @@ public UrlJwkProvider(URL url, Integer connectTimeout, Integer readTimeout, Prox
6784
this.proxy = proxy;
6885
this.connectTimeout = connectTimeout;
6986
this.readTimeout = readTimeout;
87+
this.sslSocketFactory = sslSocketFactory;
7088
this.reader = new ObjectMapper().readerFor(Map.class);
7189

7290
this.headers = (headers == null) ?
@@ -126,6 +144,9 @@ static URL urlForDomain(String domain) {
126144
private Map<String, Object> getJwks() throws SigningKeyNotFoundException {
127145
try {
128146
final URLConnection c = (proxy == null) ? this.url.openConnection() : this.url.openConnection(proxy);
147+
if (c instanceof HttpsURLConnection && sslSocketFactory != null) {
148+
((HttpsURLConnection) c).setSSLSocketFactory(sslSocketFactory);
149+
}
129150
if (connectTimeout != null) {
130151
c.setConnectTimeout(connectTimeout);
131152
}

src/test/java/com/auth0/jwk/JwkProviderBuilderTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import java.util.Collections;
1111
import java.util.Map;
1212
import java.util.concurrent.TimeUnit;
13+
import javax.net.ssl.SSLSocketFactory;
1314

1415
import static com.auth0.jwk.UrlJwkProvider.WELL_KNOWN_JWKS_PATH;
1516
import static org.hamcrest.MatcherAssert.assertThat;
1617
import static org.hamcrest.Matchers.*;
18+
import static org.mockito.Mockito.mock;
1719

1820
public class JwkProviderBuilderTest {
1921

@@ -195,4 +197,30 @@ public void shouldCreateForUrlWithCustomHeaders() throws Exception {
195197
UrlJwkProvider urlJwkProvider = (UrlJwkProvider) provider;
196198
assertThat(urlJwkProvider.headers, equalTo(headers));
197199
}
200+
201+
@Test
202+
public void shouldCreateForUrlWithSSLSocketFactory() throws Exception {
203+
URL url = new URL(normalizedDomain + WELL_KNOWN_JWKS_PATH);
204+
SSLSocketFactory sslSocketFactory = mock(SSLSocketFactory.class);
205+
JwkProvider provider = new JwkProviderBuilder(url)
206+
.sslSocketFactory(sslSocketFactory)
207+
.rateLimited(false)
208+
.cached(false)
209+
.build();
210+
assertThat(provider, notNullValue());
211+
UrlJwkProvider urlJwkProvider = (UrlJwkProvider) provider;
212+
assertThat(urlJwkProvider.sslSocketFactory, equalTo(sslSocketFactory));
213+
}
214+
215+
@Test
216+
public void shouldDefaultSSLSocketFactoryToNull() throws Exception {
217+
URL url = new URL(normalizedDomain + WELL_KNOWN_JWKS_PATH);
218+
JwkProvider provider = new JwkProviderBuilder(url)
219+
.rateLimited(false)
220+
.cached(false)
221+
.build();
222+
assertThat(provider, notNullValue());
223+
UrlJwkProvider urlJwkProvider = (UrlJwkProvider) provider;
224+
assertThat(urlJwkProvider.sslSocketFactory, is(nullValue()));
225+
}
198226
}

src/test/java/com/auth0/jwk/UrlJwkProviderTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import java.util.HashMap;
1616
import java.util.List;
1717
import java.util.Map;
18+
import javax.net.ssl.HttpsURLConnection;
19+
import javax.net.ssl.SSLSocketFactory;
1820

1921
import static com.auth0.jwk.UrlJwkProvider.WELL_KNOWN_JWKS_PATH;
2022
import static org.hamcrest.Matchers.*;
@@ -404,4 +406,37 @@ public void shouldFetchIfCacheIsNull() throws Exception {
404406
verify(provider, atLeastOnce()).getAll(); // Should definitely be called
405407
}
406408

409+
@Test
410+
public void shouldConfigureSSLSocketFactoryForHttpsConnection() throws Exception {
411+
HttpsURLConnection httpsUrlConnection = mock(HttpsURLConnection.class);
412+
when(httpsUrlConnection.getInputStream()).thenAnswer(new Answer<Object>() {
413+
@Override
414+
public Object answer(InvocationOnMock invocation) throws Throwable {
415+
return getClass().getResourceAsStream("/jwks.json");
416+
}
417+
});
418+
419+
SSLSocketFactory sslSocketFactory = mock(SSLSocketFactory.class);
420+
URL url = getClass().getResource("/jwks.json");
421+
UrlJwkProvider provider = new UrlJwkProvider(url, null, null, null, null, sslSocketFactory);
422+
423+
assertThat(provider.sslSocketFactory, is(sslSocketFactory));
424+
}
425+
426+
@Test
427+
public void shouldDefaultSSLSocketFactoryToNull() throws Exception {
428+
URL url = getClass().getResource("/jwks.json");
429+
UrlJwkProvider provider = new UrlJwkProvider(url);
430+
431+
assertThat(provider.sslSocketFactory, is(nullValue()));
432+
}
433+
434+
@Test
435+
public void shouldDefaultSSLSocketFactoryToNullWith5ArgConstructor() throws Exception {
436+
URL url = getClass().getResource("/jwks.json");
437+
UrlJwkProvider provider = new UrlJwkProvider(url, null, null, null, null);
438+
439+
assertThat(provider.sslSocketFactory, is(nullValue()));
440+
}
441+
407442
}

0 commit comments

Comments
 (0)