简单的 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
Simple Java HTTPS server
提问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 HttpsServer
class in Java 6 SE but I'm struggling to get it working. As a test client, I'm using wget
from the cygwin command line (wget https://[address]:[port]
) but wget
reports that it was "Unable to establish SSL connection".
我打算HttpsServer
在 Java 6 SE 中使用这个类,但我正在努力让它工作。作为测试客户端,我wget
从 cygwin 命令行 ( wget https://[address]:[port]
) 使用,但wget
报告“无法建立 SSL 连接”。
If I run wget
with the -d
option 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.load
and kmf.init
must use storepass and keypass, respectively.
可能 storepass 和 keypass 可能不同,在这种情况下,ks.load
和kmf.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.HttpsServer
in 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 HttpsServer
is 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 keytool
to 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。