diff --git a/jooby/src/main/java/io/jooby/ServerOptions.java b/jooby/src/main/java/io/jooby/ServerOptions.java index 23ee556731..9e2c264058 100644 --- a/jooby/src/main/java/io/jooby/ServerOptions.java +++ b/jooby/src/main/java/io/jooby/ServerOptions.java @@ -536,12 +536,18 @@ public ServerOptions setExpectContinue(@Nullable Boolean expectContinue) { /** * Creates SSL context using the given resource loader. This method attempts to create a - * SSLContext when: + * SSLContext when one of the following is true: * - *
- {@link #getSecurePort()} has been set; or - {@link #getSsl()} has been set. + *
If secure port is set and there is no SSL options, this method configure a SSL context using + *
+ * If secure port is set and there is no SSL options, this method configure a SSL context using
* the a self-signed certificate for localhost
.
+ *
+ * If {@link SslOptions#getCustomSslContext()} is set, it is returned without modification.
*
* @param loader Resource loader.
* @return SSLContext or
+ * If a custom SSL Context is set, all options except for {@link #getClientAuth()} and {@link #getProtocol()} are ignored.
+ *
+ * @return the custom SSL Context or null
+ */
+ public @Nullable SSLContext getCustomSslContext() {
+ return customSslContext;
+ }
+
+ /**
+ * Sets a custom SSL context.
+ *
+ * If a custom SSL Context is set, all options except for {@link #getClientAuth()} and {@link #getProtocol()} are ignored.
+ *
+ * @param customSslContext the new context or null to unset it
+ */
+ public void setCustomSslContext(@Nullable SSLContext customSslContext) {
+ this.customSslContext = customSslContext;
+ }
+
@Override
public String toString() {
return type;
diff --git a/tests/src/test/java/io/jooby/test/HttpsTest.java b/tests/src/test/java/io/jooby/test/HttpsTest.java
index a47dd22615..d7c35ff454 100644
--- a/tests/src/test/java/io/jooby/test/HttpsTest.java
+++ b/tests/src/test/java/io/jooby/test/HttpsTest.java
@@ -5,14 +5,14 @@
*/
package io.jooby.test;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
import io.jooby.ServerOptions;
import io.jooby.SslOptions;
import io.jooby.handler.SSLHandler;
import io.jooby.junit.ServerTest;
import io.jooby.junit.ServerTestRunner;
+import static org.junit.jupiter.api.Assertions.*;
+
public class HttpsTest {
@ServerTest
@@ -237,4 +237,30 @@ public void httpsOnly(ServerTestRunner runner) {
https.get("/test", rsp -> assertEquals("test", rsp.body().string()));
});
}
+
+ @ServerTest
+ public void customSslContext(ServerTestRunner runner) {
+ runner
+ .define(
+ app -> {
+ var options = new ServerOptions().setSecurePort(8443).setHttpsOnly(true);
+ options.setSsl(SslOptions.selfSigned());
+ // a fresh context is created every time based on config
+ var ctx1 = options.getSSLContext(this.getClass().getClassLoader());
+ var ctx2 = options.getSSLContext(this.getClass().getClassLoader());
+ assertNotSame(ctx1, ctx2);
+
+ // now always the configured context is returned
+ options.getSsl().setCustomSslContext(ctx1);
+ assertSame(ctx1, options.getSSLContext(this.getClass().getClassLoader()));
+ assertSame(ctx1, options.getSSLContext(this.getClass().getClassLoader()));
+
+ app.setServerOptions(options);
+ app.get("/test", ctx -> "test");
+ })
+ .ready(
+ (http, https) -> {
+ https.get("/test", rsp -> assertEquals("test", rsp.body().string()));
+ });
+ }
}
null
when SSL is disabled.
@@ -549,33 +555,38 @@ public ServerOptions setExpectContinue(@Nullable Boolean expectContinue) {
public @Nullable SSLContext getSSLContext(@NonNull ClassLoader loader) {
if (isSSLEnabled()) {
setSecurePort(Optional.ofNullable(securePort).orElse(SEVER_SECURE_PORT));
- setSsl(Optional.ofNullable(ssl).orElseGet(SslOptions::selfSigned));
- SslOptions options = getSsl();
-
- SslContextProvider sslContextProvider =
- Stream.of(SslContextProvider.providers())
- .filter(it -> it.supports(options.getType()))
- .findFirst()
- .orElseThrow(
- () -> new UnsupportedOperationException("SSL Type: " + options.getType()));
-
- String providerName =
- stream(
- spliteratorUnknownSize(
- ServiceLoader.load(SslProvider.class).iterator(), Spliterator.ORDERED),
- false)
- .findFirst()
- .map(
- provider -> {
- String name = provider.getName();
- if (Security.getProvider(name) == null) {
- Security.addProvider(provider.create());
- }
- return name;
- })
- .orElse(null);
-
- SSLContext sslContext = sslContextProvider.create(loader, providerName, options);
+ SslOptions options = Optional.ofNullable(ssl).orElseGet(SslOptions::selfSigned);
+ setSsl(options);
+
+ SSLContext sslContext;
+ if (options.getCustomSslContext() == null) {
+ SslContextProvider sslContextProvider =
+ Stream.of(SslContextProvider.providers())
+ .filter(it -> it.supports(options.getType()))
+ .findFirst()
+ .orElseThrow(
+ () -> new UnsupportedOperationException("SSL Type: " + options.getType()));
+
+ String providerName =
+ stream(
+ spliteratorUnknownSize(
+ ServiceLoader.load(SslProvider.class).iterator(), Spliterator.ORDERED),
+ false)
+ .findFirst()
+ .map(
+ provider -> {
+ String name = provider.getName();
+ if (Security.getProvider(name) == null) {
+ Security.addProvider(provider.create());
+ }
+ return name;
+ })
+ .orElse(null);
+
+ sslContext = sslContextProvider.create(loader, providerName, options);
+ } else {
+ sslContext = options.getCustomSslContext();
+ }
// validate TLS protocol, at least one protocol must be supported
Setnull
).
+ *