简单的 Java HTTPS 服务器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2308479/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 05:57:04  来源:igfitidea点击:

Simple Java HTTPS server

javasslhttpsjava-6

提问by Andrew

I need to set up a really lightweight HTTPS server for a Java application. It's a simulator that's being used in our development labs to simulate the HTTPS connections accepted by a piece of equipment in the wild. Because it's purely a lightweight development tool and isn't used in production in any way at all, I'm quite happy to bypass certifications and as much negotiation as I can.

我需要为 Java 应用程序设置一个真正轻量级的 HTTPS 服务器。这是一个模拟器,我们的开发实验室正在使用它来模拟一台设备在野外接受的 HTTPS 连接。因为它纯粹是一个轻量级的开发工具,根本没有以任何方式用于生产,所以我很乐意绕过认证和尽可能多的谈判。

I'm planning on using the HttpsServerclass in Java 6 SE but I'm struggling to get it working. As a test client, I'm using wgetfrom the cygwin command line (wget https://[address]:[port]) but wgetreports that it was "Unable to establish SSL connection".

我打算HttpsServer在 Java 6 SE 中使用这个类,但我正在努力让它工作。作为测试客户端,我wget从 cygwin 命令行 ( wget https://[address]:[port]) 使用,但wget报告“无法建立 SSL 连接”。

If I run wgetwith the -doption for debugging it tells me "SSL handshake failed".

如果我wget使用-d调试选项运行,它会告诉我“SSL 握手失败”。

I've spent 30 minutes googling this and everything seems to just point back to the fairly useless Java 6 documentation that describes the methods but doesn't actually talk about how to get the darn thing talking or provide any example code at all.

我花了 30 分钟在谷歌上搜索这个,一切似乎都指向了描述方法的相当无用的 Java 6 文档,但实际上并没有谈论如何让该死的事情说话或提供任何示例代码。

Can anyone nudge me in the right direction?

谁能把我推向正确的方向?

采纳答案by Andrew

What I eventually used was this:

我最终使用的是这个:

try {
    // Set up the socket address
    InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), config.getHttpsPort());

    // Initialise the HTTPS server
    HttpsServer httpsServer = HttpsServer.create(address, 0);
    SSLContext sslContext = SSLContext.getInstance("TLS");

    // Initialise the keystore
    char[] password = "simulator".toCharArray();
    KeyStore ks = KeyStore.getInstance("JKS");
    FileInputStream fis = new FileInputStream("lig.keystore");
    ks.load(fis, password);

    // Set up the key manager factory
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(ks, password);

    // Set up the trust manager factory
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    tmf.init(ks);

    // Set up the HTTPS context and parameters
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
    httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
        public void configure(HttpsParameters params) {
            try {
                // Initialise the SSL context
                SSLContext c = SSLContext.getDefault();
                SSLEngine engine = c.createSSLEngine();
                params.setNeedClientAuth(false);
                params.setCipherSuites(engine.getEnabledCipherSuites());
                params.setProtocols(engine.getEnabledProtocols());

                // Get the default parameters
                SSLParameters defaultSSLParameters = c.getDefaultSSLParameters();
                params.setSSLParameters(defaultSSLParameters);
            } catch (Exception ex) {
                ILogger log = new LoggerFactory().getLogger();
                log.exception(ex);
                log.error("Failed to create HTTPS port");
            }
        }
    });
    LigServer server = new LigServer(httpsServer);
    joinableThreadList.add(server.getJoinableThread());
} catch (Exception exception) {
    log.exception(exception);
    log.error("Failed to create HTTPS server on port " + config.getHttpsPort() + " of localhost");
}

To generate a keystore:

要生成密钥库:

$ keytool -genkeypair -keyalg RSA -alias self_signed -keypass simulator \
  -keystore lig.keystore -storepass simulator

See also here.

另请参见此处

Potentially storepass and keypass might be different, in which case the ks.loadand kmf.initmust use storepass and keypass, respectively.

可能 storepass 和 keypass 可能不同,在这种情况下,ks.loadkmf.init必须分别使用 storepass 和 keypass。

回答by krishnakumar sekar

I updated your answer for a HTTPS server (not socket-based). It might help with CSRF and AJAX calls.

我更新了 HTTPS 服务器(不是基于套接字的)的答案。它可能有助于 CSRF 和 AJAX 调用。

import java.io.*;
import java.net.InetSocketAddress;
import java.lang.*;
import java.net.URL;
import com.sun.net.httpserver.HttpsServer;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import com.sun.net.httpserver.*;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;

import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URLConnection;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

import java.net.InetAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsExchange;

public class SimpleHTTPSServer {

    public static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "This is the response";
            HttpsExchange httpsExchange = (HttpsExchange) t;
            t.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
            t.sendResponseHeaders(200, response.getBytes().length);
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {

        try {
            // setup the socket address
            InetSocketAddress address = new InetSocketAddress(8000);

            // initialise the HTTPS server
            HttpsServer httpsServer = HttpsServer.create(address, 0);
            SSLContext sslContext = SSLContext.getInstance("TLS");

            // initialise the keystore
            char[] password = "password".toCharArray();
            KeyStore ks = KeyStore.getInstance("JKS");
            FileInputStream fis = new FileInputStream("testkey.jks");
            ks.load(fis, password);

            // setup the key manager factory
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, password);

            // setup the trust manager factory
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init(ks);

            // setup the HTTPS context and parameters
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
            httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
                public void configure(HttpsParameters params) {
                    try {
                        // initialise the SSL context
                        SSLContext context = getSSLContext();
                        SSLEngine engine = context.createSSLEngine();
                        params.setNeedClientAuth(false);
                        params.setCipherSuites(engine.getEnabledCipherSuites());
                        params.setProtocols(engine.getEnabledProtocols());

                        // Set the SSL parameters
                        SSLParameters sslParameters = context.getSupportedSSLParameters();
                        params.setSSLParameters(sslParameters);

                    } catch (Exception ex) {
                        System.out.println("Failed to create HTTPS port");
                    }
                }
            });
            httpsServer.createContext("/test", new MyHandler());
            httpsServer.setExecutor(null); // creates a default executor
            httpsServer.start();

        } catch (Exception exception) {
            System.out.println("Failed to create HTTPS server on port " + 8000 + " of localhost");
            exception.printStackTrace();

        }
    }

}

To create a self-signed certificate:

要创建自签名证书:

keytool -genkeypair -keyalg RSA -alias selfsigned -keystore testkey.jks -storepass password -validity 360 -keysize 2048

回答by amichair

Just a reminder to others: com.sun.net.httpserver.HttpsServerin the solutions above is not part of the Java standard. Although it is bundledwith the Oracle/OpenJDK JVM, it is not included in all JVMs so this will not work out of the box everywhere.

只是提醒其他人:com.sun.net.httpserver.HttpsServer上述解决方案不是 Java 标准的一部分。尽管它与 Oracle/OpenJDK JVM捆绑在一起,但并未包含在所有 JVM 中,因此这不会在任何地方开箱即用。

There are several lightweight HTTP servers out there that you can embed in your application that support HTTPS and run on any JVM.

有几个轻量级 HTTP 服务器可以嵌入到支持 HTTPS 并在任何 JVM 上运行的应用程序中。

One of them is JLHTTP - The Java Lightweight HTTP Serverwhich is a tiny one-file server (or ~50K/35K jar) with no dependencies. Setting up the keystore, SSLContext etc. is similar to the above, since it also relies on the standard JSSE implementation, or you can specify the standard system properties to configure SSL. You can see the FAQor the code and its documentation for details.

其中之一是JLHTTP - Java 轻量级 HTTP 服务器,它是一个没有依赖项的小型单文件服务器(或 ~50K/35K jar)。设置密钥库、SSLContext 等与上面类似,因为它也依赖于标准 JSSE 实现,或者您可以指定标准系统属性来配置 SSL。您可以查看常见问题解答或代码及其文档以了解详细信息。

Disclaimer: I'm the author of JLHTTP. You can check it out for yourself and determine if it suits your needs. I hope you find it useful :-)

免责声明:我是 JLHTTP 的作者。您可以自行检查并确定它是否适合您的需求。希望对你有帮助 :-)

回答by Matthias Braun

With ServerSocket

ServerSocket

You can use the class that HttpsServeris built around to be even more light-weight: ServerSocket.

您可以使用HttpsServer围绕构建的类来实现更轻量级:ServerSocket.

Single-threaded

单线程

The following program is a very simple, single-threaded server listening on port 8443. Messages are encrypted with TLS using the keys in ./keystore.jks:

以下程序是一个非常简单的单线程服务器,侦听端口 8443。使用 TLS 中的密钥对消息进行加密./keystore.jks

public static void main(String... args) {
    var address = new InetSocketAddress("0.0.0.0", 8443);

    startSingleThreaded(address);
}

public static void startSingleThreaded(InetSocketAddress address) {

    System.out.println("Start single-threaded server at " + address);

    try (var serverSocket = getServerSocket(address)) {

        var encoding = StandardCharsets.UTF_8;

        // This infinite loop is not CPU-intensive since method "accept" blocks
        // until a client has made a connection to the socket
        while (true) {
            try (var socket = serverSocket.accept();
                 // Use the socket to read the client's request
                 var reader = new BufferedReader(new InputStreamReader(
                         socket.getInputStream(), encoding.name()));
                 // Writing to the output stream and then closing it sends
                 // data to the client
                 var writer = new BufferedWriter(new OutputStreamWriter(
                         socket.getOutputStream(), encoding.name()))
            ) {
                getHeaderLines(reader).forEach(System.out::println);

                writer.write(getResponse(encoding));
                writer.flush();

            } catch (IOException e) {
                System.err.println("Exception while handling connection");
                e.printStackTrace();
            }
        }
    } catch (Exception e) {
        System.err.println("Could not create socket at " + address);
        e.printStackTrace();
    }
}

private static ServerSocket getServerSocket(InetSocketAddress address)
        throws Exception {

    // Backlog is the maximum number of pending connections on the socket,
    // 0 means that an implementation-specific default is used
    int backlog = 0;

    var keyStorePath = Path.of("./keystore.jks");
    char[] keyStorePassword = "pass_for_self_signed_cert".toCharArray();

    // Bind the socket to the given port and address
    var serverSocket = getSslContext(keyStorePath, keyStorePassword)
            .getServerSocketFactory()
            .createServerSocket(address.getPort(), backlog, address.getAddress());

    // We don't need the password anymore → Overwrite it
    Arrays.fill(keyStorePassword, '0');

    return serverSocket;
}

private static SSLContext getSslContext(Path keyStorePath, char[] keyStorePass)
        throws Exception {

    var keyStore = KeyStore.getInstance("JKS");
    keyStore.load(new FileInputStream(keyStorePath.toFile()), keyStorePass);

    var keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
    keyManagerFactory.init(keyStore, keyStorePass);

    var sslContext = SSLContext.getInstance("TLS");
    // Null means using default implementations for TrustManager and SecureRandom
    sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
    return sslContext;
}

private static String getResponse(Charset encoding) {
    var body = "The server says hi \r\n";
    var contentLength = body.getBytes(encoding).length;

    return "HTTP/1.1 200 OK\r\n" +
            String.format("Content-Length: %d\r\n", contentLength) +
            String.format("Content-Type: text/plain; charset=%s\r\n",
                    encoding.displayName()) +
            // An empty line marks the end of the response's header
            "\r\n" +
            body;
}

private static List<String> getHeaderLines(BufferedReader reader)
        throws IOException {
    var lines = new ArrayList<String>();
    var line = reader.readLine();
    // An empty line marks the end of the request's header
    while (!line.isEmpty()) {
        lines.add(line);
        line = reader.readLine();
    }
    return lines;
}

Here's a projectusing this socket-based approach.

这是一个使用这种基于套接字的方法的项目

Multi-threaded

多线程

To use more than one thread for the server, you can employ a thread pool:

要为服务器使用多个线程,您可以使用线程池

public static void startMultiThreaded(InetSocketAddress address) {

    try (var serverSocket = getServerSocket(address)) {

        System.out.println("Started multi-threaded server at " + address);

        // A cached thread pool with a limited number of threads
        var threadPool = newCachedThreadPool(8);

        var encoding = StandardCharsets.UTF_8;

        // This infinite loop is not CPU-intensive since method "accept" blocks
        // until a client has made a connection to the socket
        while (true) {
            try {
                var socket = serverSocket.accept();
                // Create a response to the request on a separate thread to
                // handle multiple requests simultaneously
                threadPool.submit(() -> {

                    try ( // Use the socket to read the client's request
                          var reader = new BufferedReader(new InputStreamReader(
                                  socket.getInputStream(), encoding.name()));
                          // Writing to the output stream and then closing it
                          // sends data to the client
                          var writer = new BufferedWriter(new OutputStreamWriter(
                                  socket.getOutputStream(), encoding.name()))
                    ) {
                        getHeaderLines(reader).forEach(System.out::println);
                        writer.write(getResponse(encoding));
                        writer.flush();
                        // We're done with the connection → Close the socket
                        socket.close();

                    } catch (Exception e) {
                        System.err.println("Exception while creating response");
                        e.printStackTrace();
                    }
                });
            } catch (IOException e) {
                System.err.println("Exception while handling connection");
                e.printStackTrace();
            }
        }
    } catch (Exception e) {
        System.err.println("Could not create socket at " + address);
        e.printStackTrace();
    }
}

private static ExecutorService newCachedThreadPool(int maximumNumberOfThreads) {
    return new ThreadPoolExecutor(0, maximumNumberOfThreads,
            60L, TimeUnit.SECONDS,
            new SynchronousQueue<>());
}

Create a certificate

创建证书

Use the keytoolto create a self-signed certificate (you can get a proper certificate from Let's Encryptfor free):

使用keytool创建自签名证书(您可以免费从Let's Encrypt获得正确的证书):

keytool -genkeypair -keyalg RSA -alias selfsigned -keystore keystore.jks \
        -storepass pass_for_self_signed_cert \
        -dname "CN=localhost, OU=Developers, O=Bull Bytes, L=Linz, C=AT"

Contact the server

联系服务器

After starting the server, connect to it with curl:

启动服务器后,使用curl连接到它:

curl -k https://localhost:8443

This will fetch a message from the server:

这将从服务器获取一条消息:

The server says hi

服务器打招呼

Inspect which protocol and cipher suite were established by curl and your server with

检查 curl 和您的服务器建立了哪些协议和密码套件

curl -kv https://localhost:8443

Using JDK 13 and curl 7.66.0, this produced

使用 JDK 13 和 curl 7.66.0,这产生了

SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384

使用 TLSv1.3 / TLS_AES_256_GCM_SHA384 的 SSL 连接



Refer to Java Network Programmingby Elliotte Rusty Harold for more on the topic.

有关该主题的更多信息,请参阅Elliotte Rusty Harold 的Java Network Programming