J'ai besoin de configurer un serveur HTTPS vraiment léger pour une application Java. C'est un simulateur qui est utilisé dans nos laboratoires de développement pour simuler les connexions HTTPS acceptées par un équipement dans la nature. Parce que c'est purement un outil de développement léger et n'est pas utilisé du tout en production, je suis très heureux de contourner les certifications et autant de négociations que possible.
Je prévois d'utiliser la classe HttpsServer
dans Java 6 SE mais j'ai du mal à le faire fonctionner. En tant que client de test, j'utilise wget
depuis la ligne de commande cygwin (wget https://[address]:[port]
) mais wget
signale qu'il était "Impossible d'établir une connexion SSL".
Si je lance wget
avec le -d
L'option de débogage me dit "La prise de contact SSL a échoué".
J'ai passé 30 minutes à googler cela et tout semble simplement renvoyer à la documentation Java 6 assez inutile qui décrit les méthodes mais ne parle pas réellement de la façon de faire parler de la chose sacrément ou fournir un exemple de code.
Quelqu'un peut-il me pousser dans la bonne direction?
Ce que j'ai finalement utilisé était le suivant:
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");
}
Pour générer un fichier de clés:
$ keytool -genkeypair -keyalg RSA -alias self_signed -keypass simulator \
-keystore lig.keystore -storepass simulator
Voir aussi ici .
Potentiellement storepass et keypass peuvent être différents, auquel cas le ks.load
et kmf.init
doit utiliser respectivement storepass et keypass.
J'ai mis à jour votre réponse pour un serveur HTTPS (non basé sur un socket). Cela pourrait aider avec CSRF et AJAX appels.
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();
}
}
}
Pour créer un certificat auto-signé:
keytool -genkeypair -keyalg RSA -alias selfsigned -keystore testkey.jks -storepass password -validity 360 -keysize 2048
Juste un rappel aux autres: com.Sun.net.httpserver.HttpsServer
dans les solutions ci-dessus ne fait pas partie de la norme Java. Bien qu'il soit fourni avec la JVM Oracle/OpenJDK, il n'est pas inclus dans toutes les JVM, donc cette ne fonctionnera pas hors de la boîte partout.
Il existe plusieurs serveurs HTTP légers que vous pouvez intégrer dans votre application qui prennent en charge HTTPS et s'exécuter sur n'importe quelle machine virtuelle Java.
L'un d'eux est JLHTTP - Le Java Lightweight HTTP Server qui est un petit serveur à un fichier (ou ~ 50K/35K jar) sans dépendances. keystore, SSLContext etc. est similaire à ce qui précède, car il repose également sur l'implémentation JSSE standard, ou vous pouvez spécifier les propriétés système standard pour configurer SSL. Vous pouvez voir la FAQ ou le code et sa documentation pour plus de détails.
Avertissement: je suis l'auteur de JLHTTP. Vous pouvez le vérifier par vous-même et déterminer s'il convient à vos besoins. J'espère que tu trouves cela utile :-)
ServerSocket
Vous pouvez utiliser la classe qui HttpsServer
est construite pour être encore plus légère: ServerSocket
.
Le programme suivant est un serveur à thread unique très simple qui écoute sur le port 8443. Les messages sont chiffrés avec TLS à l'aide des clés dans ./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;
}
Pour utiliser plusieurs threads pour le serveur, vous pouvez utiliser un pool de threads :
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<>());
}
Utilisez keytool
pour créer un certificat auto-signé (vous pouvez obtenir un certificat approprié auprès de Let's Encrypt gratuitement):
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"
Après avoir démarré le serveur, connectez-vous avec curl :
curl -k https://localhost:8443
Cela va chercher un message sur le serveur:
Le serveur dit bonjour ????
Vérifiez quel protocole et quelle suite de chiffrement ont été établis par curl et votre serveur avec
curl -kv https://localhost:8443
En utilisant JDK 11 et curl 7.65.1, cela a produit
Connexion SSL à l'aide de TLSv1.3/TLS_AES_256_GCM_SHA384
Reportez-vous à Java Network Programming par Elliotte Rusty Harold pour plus d'informations sur le sujet.