Настройка встроенного в JDK HTTP сервера для работы по HTTPS

В соответствии с документацией настраиваю тестовый https-сервер, поддерживающий одностороннюю аутентификацию (https://dev64.wordpress.com/2013/06/12/ssl-basic/).

import com.sun.net.httpserver.*;
import org.junit.Test;

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class SimpleServerTest {

    static class MyHandler implements HttpHandler {
        public void handle(HttpExchange t) throws IOException {

          String response = "This is the response ";
          t.sendResponseHeaders(200, response.length());
          OutputStream os = t.getResponseBody();
          os.write(response.getBytes());

        }
    }

    @Test
    public void testSimpleServer() throws IOException, KeyManagementException, NoSuchAlgorithmException, CertificateException, KeyStoreException,
               UnrecoverableKeyException, InterruptedException {

                   HttpsServer server = HttpsServer.create(new InetSocketAddress(8080), 5);
                   server.createContext("/", new MyHandler());

                   char[] storepass = "storepass".toCharArray();
                   char[] keypass = "serverpass".toCharArray();

                   KeyStore ks = KeyStore.getInstance("JKS");
                   ks.load(SimpleServerTest.class.getResourceAsStream("server.jks"), storepass);

                   KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
                   kmf.init(ks, keypass);

                   SSLContext sslContext = SSLContext.getInstance("TLS");
                   sslContext.init(kmf.getKeyManagers(), new TrustManager[]{}, null);

                  server.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
                      public void configure (HttpsParameters params) {

                          // get the remote address if needed
                          InetSocketAddress remote = params.getClientAddress();

                          SSLContext c = getSSLContext();

                          // get the default parameters
                          SSLParameters sslparams = c.getDefaultSSLParameters();

                          params.setSSLParameters(sslparams);
                          // statement above could throw IAE if any params invalid.
                          // eg. if app has a UI and parameters supplied by a user.

                      }
                  });

        //

        ExecutorService executor = Executors.newFixedThreadPool(5);
        server.setExecutor(executor);
        server.start();
        executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS);
    }
}

Теперь про код подробнее. В целом код похож на конфигурирование обычного сервера. Первые 2 строчки и последние 4-ре, совпадают. В середине добавляется конфигурирование, относящееся к Https.

HttpsServer server = HttpsServer.create(new InetSocketAddress(8080), 5);
server.createContext("/", new MyHandler());

Создается сервер, слушающий на 8080 порту с очередью на 5 одновременных соединений. Настраивается обработчик запросов MyHandler, который отвечает на все запросы к серверу.

char[] storepass = "storepass".toCharArray();
char[] keypass = "serverpass".toCharArray();

KeyStore ks = KeyStore.getInstance("JKS");
ks.load(SimpleServerTest.class.getResourceAsStream("server.jks"), storepass);

KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, keypass);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), new TrustManager[]{}, null);

Создается SSLContext объект на основе массивов KeyManager-ов и TrustManager-ов. Т.к. я делаю эксперимент с односторонней аутентификацией, мне не нужны TrustManager-ы. Однако, сервер должен себя идентифицировать, поэтому KeyManager хотя бы один необходим обязательно. Он и создается на основе keystore, созданного, как было описано в предыдущем посте (https://dev64.wordpress.com/2013/06/17/ssl-keystore-java/).

Тонкий момент здесь — пароли. Их два, один пароль для чтения keystore, указывается в фунции load(). Второй пароль к алиасу (ключу внутри keystore). Этот пароль указывается в функцию init(). Этот момент из примера в документации на HttpsServer не совсем очевиден. Там применяется одинаковая строчка для обеих паролей. В нашем случае это «storepass» и «serverpass» соответственно.

У сервера также устанавливается некий HttpsConfigurator. Пример его взят из документации. С помощью него для соединения устанавливаются SSL параметры. В зависимости от IP адреса клиента, к примеру, параметры могут меняться. В нашем случае ничего этого не надо.

server.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
    public void configure (HttpsParameters params) {

    // get the remote address if needed
    InetSocketAddress remote = params.getClientAddress();

    SSLContext c = getSSLContext();

    // get the default parameters
    SSLParameters sslparams = c.getDefaultSSLParameters();

    params.setSSLParameters(sslparams);
    // statement above could throw IAE if any params invalid.
    // eg. if app has a UI and parameters supplied by a user.

    }
});

Запускаю сервер, тестируюсь. Коннекчусь браузером на https://localhost:8080/. Браузер выводит предупреждение о том, что сертификат сервера не подписан CA. Это нормально, я использую self-signed сертификат. Говорю «продолжить» соединение нормально проходит. Отлично, часть теста пройдена.

В сhrome подключаюсь, в firefox нет.
Можно еще попробовать curl -k https://localhost:8080

Взято:
https://dev64.wordpress.com/2013/06/18/configure-embedded-jdk-http-server-for-https/