Server HTTPS Support
Apache Pekko HTTP supports TLS encryption on the server-side as well as on the client-side.
The central vehicle for configuring encryption is the HttpsConnectionContext
HttpsConnectionContext
, which can be created using the static method ConnectionContext.httpsServer
which is defined like this:
- Scala
-
source
/** * Creates an HttpsConnectionContext for server-side use from the given SSLContext. */ def httpsServer(sslContext: SSLContext): HttpsConnectionContext = // ...
- Java
-
source
/** * Creates an HttpsConnectionContext for server-side use from the given SSLContext. */ def httpsServer(sslContext: SSLContext): HttpsConnectionContext = // ...
On the server-side, the ServerBuilder
ServerBuilder
defines a method enableHttps
with an httpsContext
parameter, which can receive the HTTPS configuration in the form of an HttpsConnectionContext
instance.
For detailed documentation for client-side HTTPS support refer to Client-Side HTTPS Support.
Obtaining SSL/TLS Certificates
In order to run an HTTPS server a certificate has to be provided, which usually is either obtained from a signing authority or created by yourself for local or staging environment purposes.
Signing authorities often provide instructions on how to create a Java keystore (typically with reference to Tomcat configuration). If you want to generate your own certificates, the official Oracle documentation on how to generate a keystore using the JDK keytool utility can be found here.
SSL-Config provides a more targeted guide on generating certificates, so we recommend you start with the guide titled Generating X.509 Certificates.
Using HTTPS
Once you have obtained the server certificate, using it is as simple as preparing an HttpsConnectionContext
HttpsConnectionContext
and passing it to enableHttps
when binding the server.
The below example shows how setting up HTTPS works. First, you create and configure an instance of HttpsConnectionContext
HttpsConnectionContext
:
- Scala
-
source
import java.io.InputStream import java.security.{ KeyStore, SecureRandom } import javax.net.ssl.{ KeyManagerFactory, SSLContext, TrustManagerFactory } import org.apache.pekko import pekko.actor.ActorSystem import pekko.http.scaladsl.server.{ Directives, Route } import pekko.http.scaladsl.{ ConnectionContext, Http, HttpsConnectionContext } implicit val system = ActorSystem() implicit val dispatcher = system.dispatcher // Manual HTTPS configuration val password: Array[Char] = "change me".toCharArray // do not store passwords in code, read them from somewhere safe! val ks: KeyStore = KeyStore.getInstance("PKCS12") val keystore: InputStream = getClass.getClassLoader.getResourceAsStream("server.p12") require(keystore != null, "Keystore required!") ks.load(keystore, password) val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509") keyManagerFactory.init(ks, password) val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509") tmf.init(ks) val sslContext: SSLContext = SSLContext.getInstance("TLS") sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom) val https: HttpsConnectionContext = ConnectionContext.httpsServer(sslContext)
- Java
-
source
// ** CONFIGURING ADDITIONAL SETTINGS ** // public static HttpsConnectionContext createHttpsContext(ActorSystem system) { try { // initialise the keystore // !!! never put passwords into code !!! final char[] password = new char[] {'a', 'b', 'c', 'd', 'e', 'f'}; final KeyStore ks = KeyStore.getInstance("PKCS12"); final InputStream keystore = SimpleServerApp.class .getClassLoader() .getResourceAsStream("httpsDemoKeys/keys/server.p12"); if (keystore == null) { throw new RuntimeException("Keystore required!"); } ks.load(keystore, password); final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); keyManagerFactory.init(ks, password); final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(ks); final SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init( keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); return ConnectionContext.httpsServer(sslContext); } catch (Exception e) { throw new RuntimeException(e); } }
After that you can pass it to enableHttps
, like displayed below:
- Scala
-
source
val routes: Route = get { complete("Hello world!") } Http().newServerAt("127.0.0.1", 8080).enableHttps(https).bind(routes)
- Java
-
source
Http.get(system) .newServerAt("localhost", 8080) .enableHttps(createHttpsContext(system)) .bind(app.createRoute());
Running both HTTP and HTTPS
If you want to run HTTP and HTTPS servers in a single application, you first create an instance of HttpsConnectionContext
as explained above and then create two server bindings for different ports, one with https enabled and one without:
- Scala
-
source
// you can run both HTTP and HTTPS in the same application as follows: val commonRoutes: Route = get { complete("Hello world!") } Http().newServerAt("127.0.0.1", 443).enableHttps(https).bind(commonRoutes) Http().newServerAt("127.0.0.1", 80).bind(commonRoutes)
- Java
-
source
final Http http = Http.get(system); // Run HTTP server firstly http.newServerAt("localhost", 80).bind(route); // get configured HTTPS context HttpsConnectionContext httpsContext = SimpleServerApp.createHttpsContext(system); // Then run HTTPS server http.newServerAt("localhost", 443).enableHttps(httpsContext).bind(route);
Mutual authentication
To require clients to authenticate themselves when connecting, you must set this on the SSLEngine
:
- Scala
-
source
val sslContext: SSLContext = ??? ConnectionContext.httpsServer(() => { val engine = sslContext.createSSLEngine() engine.setUseClientMode(false) engine.setNeedClientAuth(true) // or: engine.setWantClientAuth(true) engine })
- Java
-
source
ConnectionContext.httpsServer( () -> { SSLEngine engine = sslContext.createSSLEngine(); engine.setUseClientMode(false); engine.setNeedClientAuth(true); // or: engine.setWantClientAuth(true); return engine; });
For further (custom) certificate checks, you can access the javax.net.ssl.SSLSession
via the sslSession
attribute.
At this point dynamic renegotiation of the certificates to be used is not implemented. For details see issue #18351 and some preliminary work in PR #19787.
Further reading
The topic of properly configuring HTTPS for your web server is an always changing one, thus we recommend staying up to date with various security breach news and of course keep your JVM at the latest version possible, as the default settings are often updated by Oracle in reaction to various security updates and known issues.
We also recommend having a look at the Play documentation about securing your app, as well as the techniques described in the Play documentation about setting up a reverse proxy to terminate TLS in front of your application instead of terminating TLS inside the JVM, and therefore Apache Pekko HTTP, itself.
Other excellent articles on the subject: