简单Java HTTPS服务器ServerSocket

问题描述 投票:42回答:4

我需要为Java应用程序设置一个非常轻量级的HTTPS服务器。这是一个模拟器,我们的开发实验室已使用该模拟器来模拟野外某台设备接受的HTTPS连接。因为它纯粹是一个轻量级的开发工具,并且根本不用于生产中,所以我很乐意绕过认证和尽可能多的谈判。

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

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

我花了30分钟的时间进行了搜索,所有内容似乎都指向了相当无用的Java 6文档,该文档描述了这些方法,但实际上并没有谈论如何使这些事情变得毫无意义或根本没有提供任何示例代码。

有人可以向正确的方向推我吗?

java ssl https java-6
4个回答
43
投票

我最终使用的是:

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");
}

生成密钥库:

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

另请参见here

[可能的存储密码和密钥密码可能有所不同,在这种情况下,ks.loadkmf.init必须分别使用存储密码和密钥密码。


15
投票

我更新了您对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();

        }
    }

}

创建自签名证书:

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

2
投票

仅提醒其他人:上面解决方案中的com.sun.net.httpserver.HttpsServer不是Java标准的一部分。尽管它在Oracle / OpenJDK JVM中是bundled,但并非所有JVM中都包含它,因此这并不是在所有地方都可以立即使用。

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

其中一个是JLHTTP - The Java Lightweight HTTP Server,它是一个很小的单文件服务器(或〜50K / 35K jar),没有依赖性。设置密钥库,SSLContext等与上述类似,因为它也依赖于标准的JSSE实现,或者您可以指定标准的系统属性来配置SSL。您可以查看FAQ或代码及其文档以获取详细信息。

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


0
投票

带有ServerSocket

您可以使用构建ServerSocket的类甚至更轻量级:HttpsServer

单线程

以下程序是侦听端口8443的非常简单的单线程服务器。使用HttpsServer中的密钥使用TLS对消息进行加密:

ServerSocket

多线程

要为服务器使用多个线程,可以使用./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;
}

创建证书

使用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<>()); } 免费获得适当的证书):

keytool

联系服务器

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

Let's Encrypt

这将从服务器获取消息:

服务器问好hi

检查curl和服务器使用哪个协议和密码套件建立

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"

使用JDK 11和curl 7.65.1,由此产生

使用TLSv1.3 / TLS_AES_256_GCM_SHA384的SSL连接


有关该主题的更多信息,请参阅Elliotte Rusty Harold的curl。>

© www.soinside.com 2019 - 2024. All rights reserved.