我需要为Java应用程序设置一个非常轻量级的HTTPS服务器。它是我们开发实验室中使用的模拟器,用于模拟野外设备接受的HTTPS连接。因为它纯粹是一种轻量级的开发工具,并且完全没有以任何方式用于生产,所以我很乐意绕过认证和尽可能多的协商。
我打算在Java 6 SE中使用HttpsServer
类,但我很难让它运行起来。作为测试客户端,我在cygwin命令行(wget
)中使用wget https://[address]:[port]
,但wget
报告“无法建立SSL连接”。
如果我使用wget
选项运行-d
进行调试,则告诉我“SSL握手失败”。
我花了30分钟来搜索这一切,似乎只是指向那些描述方法的相当无用的Java6文档,但实际上并没有谈论如何让讨论或者提供任何示例代码。
有人能把我推向正确的方向吗?
答案 0 :(得分:41)
我最终使用的是:
try
{
// setup 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 );
// 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 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 -genkey -alias alias -keypass simulator \
-keystore lig.keystore -storepass simulator
另见here。
潜在的storepass和keypass可能会有所不同,在这种情况下,ks.load
和kmf.init
必须分别使用storepass和keypass。
答案 1 :(得分: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 -genkey -keyalg RSA -alias selfsigned -keystore testkey.jks -storepass password -validity 360 -keysize 2048
答案 2 :(得分:2)
只是提醒其他人:上面的解决方案中的com.sun.net.httpserver.HttpsServer
不是Java标准的一部分,而是专有的,仅与Sun / Oracle JVM捆绑在一起,因此这不适用于任何其他Java运行时。
有几个轻量级HTTP服务器可以嵌入到支持HTTPS并在任何JVM上运行的应用程序中。
其中一个是JLHTTP - The Java Lightweight HTTP Server,它是一个很小的单文件服务器(或~50K / 35K jar),没有依赖关系。设置密钥库,SSLContext等与上面类似,因为它还依赖于标准的JSSE实现,或者您可以指定标准系统属性来配置SSL。您可以查看FAQ或代码及其文档以获取详细信息。
免责声明:我是JLHTTP的作者。您可以自己检查并确定它是否符合您的需求。我希望你觉得它很有用: - )
答案 3 :(得分:2)
虽然这个问题真的很老了,但有人提到了我这个话题,问是否可以简化。大多数答案都很好地展示了如何使用 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 库中的 SSLFactory 类来轻松构建 SSLContext。它由我维护。您不需要使用它,因为其他人已经提供了一种仅使用普通 Java 构建它的方法。
答案 4 :(得分:0)
ServerSocket
您可以使用HttpsServer
构建的类,以使其更加轻巧:ServerSocket
。
以下程序是侦听端口8443的非常简单的单线程服务器。使用./keystore.jks
中的密钥通过TLS对消息进行加密:
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
创建自签名证书(您可以从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
使用JDK 11和curl 7.65.1,就产生了
使用TLSv1.3 / TLS_AES_256_GCM_SHA384的SSL连接
有关该主题的更多信息,请参阅Elliotte Rusty Harold的Java Network Programming。