Java实现SSL Socket长连接方式
作者:走马川行雪
一、单向认证
1、生成服务端密钥(配置了jdk的环境变量即可用keytool命令)
命令:keytool -genkey -keystore server_ks.jks -storepass server_password -keyalg RSA -keypass server_password
结果:会生成server_ks.jks密钥文件
操作:将生成的server_ks.jks密钥文件配置到服务端
2、生成服务端证书
命令:keytool -export -keystore server_ks.jks -storepass server_password -file server.cer
结果:会生成server.cer证书文件
操作:将生成的server.cer证书文件配置到客户端
3、服务端代码
package com.ssl.server; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import java.io.*; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.security.KeyStore; public class ServerTest { public static File writeToFile1 = new File("C:\\Users\\Li\\Desktop\\temp\\配置.txt"); public static File writeToFile2 = new File("C:\\Users\\Li\\Desktop\\temp\\配置2.txt"); public static void main(String[] args) { new Thread(new ServerOne()).start(); } public static class ServerOne implements Runnable { //SSL协议版本 private static final String TLS = "TLSv1.2"; //KeyStore的类型 private static final String PROVIDER = "SunX509"; //秘钥类型,java默认是JKS private static final String STORE_TYPE = "JKS"; //秘钥的路径 private static final String KEY_STORE_NAME = "E:\\02-IDEA_Project\\Test02_HttpsServer\\server_ks.jks"; //Server的端口 private static final int DEFAULT_PORT = 8090; //自定义端口 //秘钥的密码 private static final String SERVER_KEY_STORE_PASSWORD = "server_password"; @Override public void run() { SSLServerSocket sslServerSocket = null; try { //获取SSLContext SSLContext sslContext = SSLContext.getInstance(TLS); //生成秘钥的manager KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(PROVIDER); //加载秘钥 KeyStore keyStoreOne = KeyStore.getInstance(STORE_TYPE); keyStoreOne.load(new FileInputStream(KEY_STORE_NAME), SERVER_KEY_STORE_PASSWORD.toCharArray()); //秘钥初始化 keyManagerFactory.init(keyStoreOne, SERVER_KEY_STORE_PASSWORD.toCharArray()); //初始化SSLContext sslContext.init(keyManagerFactory.getKeyManagers(), null, null); //获取SSLContext的SocketFactory sslServerSocket = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(DEFAULT_PORT); //是否开启双向验证 sslServerSocket.setNeedClientAuth(false); System.out.println("服务器已开启,等待连接 ....."); while (true) { Socket accept = sslServerSocket.accept(); accept.setKeepAlive(true); System.out.println("客户端 : " + accept.getInetAddress().getHostAddress()); try ( InputStream inputStream = accept.getInputStream(); OutputStream outputStream = accept.getOutputStream(); ) { byteMsgReader(inputStream, outputStream); // socket.shutdownOutput(); // 长连接则不关闭输出流 // socket.shutdownInput(); // 长连接则不关闭输入流 } catch (Exception e) { System.out.println(e.toString()); } } } catch (Exception e) { e.printStackTrace(); try { if (sslServerSocket != null) { sslServerSocket.close(); System.out.println("服务器关闭!"); } } catch (Exception ex) { ex.printStackTrace(); } } } } /** * 字节流-解析客户端的消息 */ public static void byteMsgReader(InputStream inputStream, OutputStream outputStream) throws Exception { BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); FileOutputStream bufferedOutputStream1 = new FileOutputStream(writeToFile1, false); FileOutputStream bufferedOutputStream2 = new FileOutputStream(writeToFile2, false); byte[] b = new byte[4096]; int i = 0; while ((i = bufferedInputStream.read(b)) > 0) { bufferedOutputStream1.write(b, 0, i); bufferedOutputStream1.flush(); if (i < b.length) { // 根据读取的长度是否满格判断当前文件是否已读取完毕 bufferedOutputStream1.close(); break; } } System.out.println("接收完成第一个文件"); byteMsgWriter(outputStream); i = 0; byte[] b2 = new byte[4096]; while ((i = bufferedInputStream.read(b2)) > 0) { bufferedOutputStream2.write(b2, 0, i); bufferedOutputStream2.flush(); if (i < b2.length) { // 根据读取的长度是否满格判断当前文件是否已读取完毕 bufferedOutputStream2.close(); break; } } System.out.println("接收完成第二个文件"); byteMsgWriter(outputStream); } /** * 字节流-发送给客户端回执消息 */ public static void byteMsgWriter(OutputStream outputStream) throws Exception { BufferedOutputStream bufferedOutputStreamBack = new BufferedOutputStream(outputStream); bufferedOutputStreamBack.write("服务端已成功接收文件.".getBytes(StandardCharsets.UTF_8)); bufferedOutputStreamBack.write("\n".getBytes(StandardCharsets.UTF_8)); bufferedOutputStreamBack.flush(); // 第一次发送消息,长连接实现方式 } }
4、客户端代码
package com.ssl.client; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManagerFactory; import java.io.*; import java.security.KeyStore; import java.security.cert.CertificateFactory; public class ClientTest { public static File readFile = new File("C:\\Users\\Li\\Desktop\\temp\\sqlV2.0.sql"); public static void main(String[] args) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new ClientOne()).start(); } public static class ClientOne implements Runnable { private static final String TLS = "TLSv1.2"; private static final String PROVIDER = "SunX509"; private static final String STORE_TYPE = "JKS"; private static final String TRUST_STORE_NAME = "E:\\02-IDEA_Project\\Test01_HttpsClient\\server.cer"; @Override public void run() { SSLSocket socket = null; try { SSLContext sslContext = SSLContext.getInstance(TLS); //生成信任证书Manager,默认系统会信任CA机构颁发的证书,自定的证书需要手动的加载 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(PROVIDER); KeyStore keyStore = KeyStore.getInstance(STORE_TYPE); keyStore.load(null); //生成验证工厂 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); //生成别名(可以随便填写) String certificateAlias = Integer.toString(0); //加载证书 keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(new FileInputStream(TRUST_STORE_NAME))); //初始化 trustManagerFactory.init(keyStore); //初始化 sslContext.init(null, trustManagerFactory.getTrustManagers(), null); socket = (SSLSocket) sslContext.getSocketFactory().createSocket("localhost", 8090); socket.setKeepAlive(true); // socket.setSoTimeout(10000); //设置超时时间 try ( InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); ) { byteMsgWriter(outputStream, inputStream); } catch (Exception ex) { ex.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); try { if (socket != null) { socket.close(); System.out.println("客户端关闭"); } } catch (Exception ex) { ex.printStackTrace(); } } } } /** * 字节流-发送给服务端 */ public static void byteMsgWriter(OutputStream outputStream, InputStream inputStream) throws Exception { BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(readFile)); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream); //mark后读取超过readlimit字节数据,mark标记就会失效 bufferedInputStream.mark(909600000); byte[] b = new byte[4096]; int i; while ((i = bufferedInputStream.read(b)) > 0) { bufferedOutputStream.write(b, 0, i); bufferedOutputStream.flush(); // 第一次发送消息,长连接实现方式 } bufferedInputStream.reset(); Thread.sleep(1); // 必须加休眠,否则第二个文件流会发生错乱 characterMsgReader(inputStream); // 从服务端接收消息 while ((i = bufferedInputStream.read(b)) > 0) { bufferedOutputStream.write(b, 0, i); bufferedOutputStream.flush(); // 第二次发送消息,长连接实现方式 } characterMsgReader(inputStream); // 从服务端接收消息 } /** * 字符流-解析服务端的回执消息-不间断解析 */ public static void characterMsgReader(InputStream inputStream) throws Exception { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = bufferedReader.readLine()) != null) { System.out.println("服务端消息: " + line); } } }
二、双向认证
1、生成客户端密钥
命令:keytool -genkey -keystore client_ks.jks -storepass client_password -keyalg RSA -keypass client_password
结果:会生成client_ks.jks密钥文件
操作:将生成的client_ks.jks密钥文件配置到客户端
2、生成客户端证书
命令:keytool -export -keystore client_ks.jks -storepass client_password -file client.cer
结果:会生成client.cer证书文件
3、将server端证书添加到serverTrust_ks.jks文件中
命令:keytool -import -keystore serverTrust_ks.jks -storepass client -file server.cer
结果:会生成serverTrust_ks.jks密钥文件
操作:将生成的serverTrust_ks.jks密钥文件配置到客户端
4、将client端证书添加到clientTrust_ks.jks文件中
命令:keytool -import -keystore clientTrust_ks.jks -storepass server -file client.cer
结果:会生成clientTrust_ks.jks密钥文件
操作:将生成的clientTrust_ks.jks密钥文件配置到服务端
5、服务端代码
package com.ssl.server; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.TrustManagerFactory; import java.io.*; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.security.KeyStore; public class Server { public static File writeToFile1 = new File("C:\\Users\\Li\\Desktop\\temp\\配置.txt"); public static File writeToFile2 = new File("C:\\Users\\Li\\Desktop\\temp\\配置2.txt"); public static void main(String[] args) { new Thread(new ServerOne()).start(); } public static class ServerOne implements Runnable { //SSL协议版本 private static final String TLS = "TLSv1.2"; //KeyStore的类型 private static final String PROVIDER = "SunX509"; //秘钥类型,java默认是JKS,Android不支持JKS,只能用BKS private static final String STORE_TYPE = "JKS"; //秘钥的路径 private static final String KEY_STORE_NAME = "E:\\02-IDEA_Project\\Test02_HttpsServer\\server_ks.jks"; private static final String TRUST_STORE_NAME = "E:\\02-IDEA_Project\\Test02_HttpsServer\\clientTrust_ks.jks"; //Server的端口 private static final int DEFAULT_PORT = 8090; //自定义端口 //秘钥的密码 private static final String SERVER_KEY_STORE_PASSWORD = "server_password"; //秘钥的密码 private static final String SERVER_TRUST_KEY_STORE_PASSWORD = "server";//密码 @Override public void run() { SSLServerSocket sslServerSocket = null; try { //获取SSLContext SSLContext sslContext = SSLContext.getInstance(TLS); //生成秘钥的manager KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(PROVIDER); //加载信任的证书 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(PROVIDER); //加载秘钥 KeyStore keyStoreOne = KeyStore.getInstance(STORE_TYPE); KeyStore keyStoreTwo = KeyStore.getInstance(STORE_TYPE); keyStoreOne.load(new FileInputStream(KEY_STORE_NAME), SERVER_KEY_STORE_PASSWORD.toCharArray()); keyStoreTwo.load(new FileInputStream(TRUST_STORE_NAME), SERVER_TRUST_KEY_STORE_PASSWORD.toCharArray()); //秘钥初始化 keyManagerFactory.init(keyStoreOne, SERVER_KEY_STORE_PASSWORD.toCharArray()); trustManagerFactory.init(keyStoreTwo); //初始化SSLContext sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); //获取SSLContext的SocketFactory sslServerSocket = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(DEFAULT_PORT); //是否开启双向验证 sslServerSocket.setNeedClientAuth(true); System.out.println("服务器已开启,等待连接 ....."); while (true) { Socket accept = sslServerSocket.accept(); accept.setKeepAlive(true); System.out.println("客户端 : " + accept.getInetAddress().getHostAddress()); try ( InputStream inputStream = accept.getInputStream(); OutputStream outputStream = accept.getOutputStream(); ) { byteMsgReader(inputStream, outputStream); // socket.shutdownOutput(); // 长连接则不关闭输出流 // socket.shutdownInput(); // 长连接则不关闭输入流 } catch (Exception e) { System.out.println(e.toString()); } } } catch (Exception e) { e.printStackTrace(); try { if (sslServerSocket != null) { sslServerSocket.close(); System.out.println("服务器关闭!"); } } catch (Exception ex) { ex.printStackTrace(); } } } } /** * 字节流-解析客户端的消息 */ public static void byteMsgReader(InputStream inputStream, OutputStream outputStream) throws Exception { BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); FileOutputStream bufferedOutputStream1 = new FileOutputStream(writeToFile1, false); FileOutputStream bufferedOutputStream2 = new FileOutputStream(writeToFile2, false); byte[] b = new byte[4096]; int i = 0; while ((i = bufferedInputStream.read(b)) > 0) { bufferedOutputStream1.write(b, 0, i); bufferedOutputStream1.flush(); if (i < b.length) { // 根据读取的长度是否满格判断当前文件是否已读取完毕 bufferedOutputStream1.close(); break; } } System.out.println("接收完成第一个文件"); byteMsgWriter(outputStream); i = 0; byte[] b2 = new byte[4096]; while ((i = bufferedInputStream.read(b2)) > 0) { bufferedOutputStream2.write(b2, 0, i); bufferedOutputStream2.flush(); if (i < b2.length) { // 根据读取的长度是否满格判断当前文件是否已读取完毕 bufferedOutputStream2.close(); break; } } System.out.println("接收完成第二个文件"); byteMsgWriter(outputStream); } /** * 字节流-发送给客户端回执消息 */ public static void byteMsgWriter(OutputStream outputStream) throws Exception { BufferedOutputStream bufferedOutputStreamBack = new BufferedOutputStream(outputStream); bufferedOutputStreamBack.write("服务端已成功接收文件.".getBytes(StandardCharsets.UTF_8)); bufferedOutputStreamBack.write("\n".getBytes(StandardCharsets.UTF_8)); bufferedOutputStreamBack.flush(); // 第一次发送消息,长连接实现方式 } }
6、客户端代码
package com.ssl.client; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManagerFactory; import java.io.*; import java.security.KeyStore; public class Client { public static File readFile = new File("C:\\Users\\Li\\Desktop\\temp\\sqlV2.0.sql"); public static void main(String[] args) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new ClientOne()).start(); } public static class ClientOne implements Runnable { private static final String TLS = "TLSv1.2"; private static final String PROVIDER = "SunX509"; private static final String STORE_TYPE = "JKS"; private static final String TRUST_STORE_NAME = "E:\\02-IDEA_Project\\Test01_HttpsClient\\serverTrust_ks.jks"; private static final String KEY_STORE_NAME = "E:\\02-IDEA_Project\\Test01_HttpsClient\\client_ks.jks"; private static final String CLIENT_KEY_STORE_PASSWORD = "client_password"; //密码 private static final String CLIENT_TRUST_KEY_STORE_PASSWORD = "client";//密码 @Override public void run() { SSLSocket socket = null; try { SSLContext sslContext = SSLContext.getInstance(TLS); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(PROVIDER); //生成信任证书Manager,默认系统会信任CA机构颁发的证书,自定的证书需要手动的加载 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(PROVIDER); KeyStore keyStoreOne = KeyStore.getInstance(STORE_TYPE); KeyStore keyStoreTwo = KeyStore.getInstance(STORE_TYPE); //加载client端密钥 keyStoreOne.load(new FileInputStream(KEY_STORE_NAME), CLIENT_KEY_STORE_PASSWORD.toCharArray()); //信任证书 keyStoreTwo.load(new FileInputStream(TRUST_STORE_NAME), CLIENT_TRUST_KEY_STORE_PASSWORD.toCharArray()); keyManagerFactory.init(keyStoreOne, CLIENT_KEY_STORE_PASSWORD.toCharArray()); trustManagerFactory.init(keyStoreTwo); //初始化 sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); //此种写法代表客户端信任所有证书 /*TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[]{}; } public void checkClientTrusted(X509Certificate[] chain, String authType) { } public void checkServerTrusted(X509Certificate[] chain, String authType) { } }}; //初始化 sslContext.init(keyManagerFactory.getKeyManagers(), trustAllCerts, null);*/ socket = (SSLSocket) sslContext.getSocketFactory().createSocket("localhost", 8090); socket.setKeepAlive(true); // socket.setSoTimeout(10000); try ( InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); ) { byteMsgWriter(outputStream, inputStream); } catch (Exception ex) { ex.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); try { if (socket != null) { socket.close(); System.out.println("客户端关闭"); } } catch (Exception ex) { ex.printStackTrace(); } } } } /** * 字节流-发送给服务端 */ public static void byteMsgWriter(OutputStream outputStream, InputStream inputStream) throws Exception { BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(readFile)); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream); //mark后读取超过readlimit字节数据,mark标记就会失效 bufferedInputStream.mark(909600000); byte[] b = new byte[4096]; int i; while ((i = bufferedInputStream.read(b)) > 0) { bufferedOutputStream.write(b, 0, i); bufferedOutputStream.flush(); // 第一次发送消息,长连接实现方式 } bufferedInputStream.reset(); Thread.sleep(1); // 必须加休眠,否则第二个文件流会发生错乱 characterMsgReader(inputStream); // 从服务端接收消息 while ((i = bufferedInputStream.read(b)) > 0) { bufferedOutputStream.write(b, 0, i); bufferedOutputStream.flush(); // 第二次发送消息,长连接实现方式 } characterMsgReader(inputStream); // 从服务端接收消息 } /** * 字符流-解析服务端的回执消息-不间断解析 */ public static void characterMsgReader(InputStream inputStream) throws Exception { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = bufferedReader.readLine()) != null) { System.out.println("服务端消息: " + line); } } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。