代码之家  ›  专栏  ›  技术社区  ›  Andrew

简单JavaHTTPS服务器

  •  38
  • Andrew  · 技术社区  · 16 年前

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

    我正计划使用 HttpsServer 类,但我很难让它正常工作。作为一个测试客户端,我使用 wget 从cygwin命令行( wget https://[address]:[port] wget

    如果我跑 wget -d 调试选项告诉我“SSL握手失败”。

    3 回复  |  直到 7 年前
        1
  •  50
  •   Matthias Braun AdamSkywalker    7 年前

    我最终使用的是:

    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.load kmf.init 必须分别使用storepass和keypass。

        2
  •  22
  •   Matthias Braun AdamSkywalker    7 年前

    我更新了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
    
        3
  •  8
  •   Matthias Braun AdamSkywalker    6 年前

    ServerSocket

    您可以使用 HttpsServer 其结构更加轻盈: 服务端 .

    单螺纹

    下面的程序是一个非常简单的单线程服务器,侦听端口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 project

    线程

    要为服务器使用多个线程,可以使用 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 要创建自签名证书(您可以从 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"
    

    联系服务器

    curl :

    curl -k https://localhost:8443
    

    这将从服务器获取消息:

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

    curl -kv https://localhost:8443
    

    使用TLSv1.3/TLS_AES_256_GCM_SHA384的SSL连接


    提到 Java Network Programming 作者:Elliotte Rusty Harold,了解更多有关该主题的信息。

        4
  •  2
  •   Matthias Braun AdamSkywalker    7 年前

    只是提醒其他人: com.sun.net.httpserver.HttpsServer 在上述解决方案中,它不是Java标准的一部分。虽然是 bundled

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

    其中之一是 JLHTTP - The Java Lightweight HTTP Server FAQ 或代码及其文档以了解详细信息。

    免责声明:我是JLHTTP的作者。你可以自己检查一下,确定它是否适合你的需要。我希望你觉得它有用:-)

        5
  •  2
  •   Hakan54    5 年前

    虽然这个问题真的很老了,但有人提到我这个话题,问我是否可以简化。大多数答案都很好地演示了如何使用sun设置一个简单的https服务器,但我希望提供一个更简单的替代方案。

    对于此设置,我假设您已经准备好密钥库和信任库。

    import com.sun.net.httpserver.HttpExchange;
    import com.sun.net.httpserver.HttpHandler;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.nio.charset.StandardCharsets;
    
    public class HelloWorldController implements HttpHandler {
    
        @Override
        public void handle(HttpExchange exchange) throws IOException {
            try (OutputStream responseBody = exchange.getResponseBody()) {
    
                exchange.getResponseHeaders().set("Content-Type", "text/plain");
    
                String payload = "Hello";
                exchange.sendResponseHeaders(200, payload.length());
                responseBody.write(payload.getBytes(StandardCharsets.UTF_8));
            }
        }
    
    }
    

    服务器配置:

    import com.sun.net.httpserver.HttpsConfigurator;
    import com.sun.net.httpserver.HttpsParameters;
    import com.sun.net.httpserver.HttpsServer;
    import nl.altindag.server.controller.HelloWorldController;
    import nl.altindag.ssl.SSLFactory;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.util.concurrent.Executors;
    
    public class App {
    
        public static void main(String[] args) throws IOException {
            SSLFactory sslFactory = SSLFactory.builder()
                    .withIdentityMaterial("keystore.jks", "secret".toCharArray())
                    .withTrustMaterial("truststore.jks", "secret".toCharArray())
                    .build();
    
            InetSocketAddress socketAddress = new InetSocketAddress(8443);
            HttpsServer httpsServer = HttpsServer.create(socketAddress, 0);
    
            httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslFactory.getSslContext()) {
                @Override
                public void configure(HttpsParameters params) {
                    params.setSSLParameters(sslFactory.getSslParameters());
                }
            });
    
            httpsServer.createContext("/api/hello", new HelloWorldController());
            httpsServer.setExecutor(Executors.newCachedThreadPool());
            httpsServer.start();
        }
    
    }
    

    我需要在这里添加一些免责声明。。。我使用来自 Github - SSLContext-Kickstart