Java网络编程系列:
Java网络编程一:Java Socket简例
Java网络编程二:Java Secure(SSL/TLS) Socket实现
Java网络编程三:Java NIO-非阻塞通信
通信端无需向对方证明自己的身份,则称该端处于“客户模式”,否则称其处于“服务器模式”,无论是客户端还是服务器端,都可处于“客户模式”或者“服务器模式”,但是对于通信双方来说,只能有一方处于“服务模式”,而另一方则必须处于“客户模式”
一,证书部分
首先使用java自带的keytool工具生成所需的证书:
1,创建服务端keystore
keytool -genkey -keystore c:\sean_app\server.jks -storepass 1234sp -keyalg RSA -keypass 1234kp
2,创建客户端keystore
keytool -genkey -keystore c:\sean_app\client.jks -storepass 1234sp -keyalg RSA -keypass 1234kp
3,导出服务端证书
keytool -export -keystore c:\sean_app\server.jks -storepass 1234sp -file c:\sean_app\server.cer
4,导出客户端证书
keytool -export -keystore c:\sean_app\client.jks -storepass 1234sp -file c:\sean_app\client.cer
5,将服务端证书导入到客户端trustkeystroe
keytool -import -keystore c:\sean_app\clientTrust.jks -storepass 1234sp -file c:\sean_app\server.cer
6,将客户端证书导入到服务端trustkeystroe
keytool -import -keystore c:\sean_app\serverTrust.jks -storepass 1234sp -file c:\sean_app\client.cer
二,代码部分
使用JDK1.5.0_22和JDK1.6.0_45测试单向认证和双向认证均通过,唯一需要引入的包为log4j-1.2.14.jar,用来记录日志
代码结构如下:
客户端认证:
package com.test.client.auth; import java.io.FileInputStream; import java.security.KeyStore; import java.util.Properties; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import com.test.server.config.Configuration; public class Auth { private static SSLContext sslContext; public static SSLContext getSSLContext() throws Exception{ Properties p = Configuration.getConfig(); String protocol = p.getProperty("protocol"); String clientTrustCerFile = p.getProperty("clientTrustCer"); String clientTrustCerPwd = p.getProperty("clientTrustCerPwd"); //Trust Key Store KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream(clientTrustCerFile), clientTrustCerPwd.toCharArray()); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); trustManagerFactory.init(keyStore); TrustManager[] tms = trustManagerFactory.getTrustManagers(); KeyManager[] kms = null; if(Configuration.getConfig().getProperty("authority").equals("2")){ String clientCerFile = p.getProperty("clientCer"); String clientCerPwd = p.getProperty("clientCerPwd"); String clientKeyPwd = p.getProperty("clientKeyPwd"); //Key Store keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream(clientCerFile), clientCerPwd.toCharArray()); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); keyManagerFactory.init(keyStore, clientKeyPwd.toCharArray()); kms = keyManagerFactory.getKeyManagers(); } sslContext = SSLContext.getInstance(protocol); sslContext.init(kms, tms, null); return sslContext; } }
客户端主程序:
package com.test.client; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Properties; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import org.apache.log4j.Logger; import com.test.client.auth.Auth; import com.test.server.config.Configuration; import com.test.tools.MyHandshakeCompletedListener; import com.test.tools.SocketIO; public class Client { static Logger logger = Logger.getLogger(Client.class); private SSLContext sslContext; private int port = 10000; private String host = "127.0.0.1"; private SSLSocket socket; private Properties p; public Client(){ try { p = Configuration.getConfig(); sslContext = Auth.getSSLContext(); SSLSocketFactory factory = (SSLSocketFactory) sslContext.getSocketFactory(); socket = (SSLSocket)factory.createSocket(); String[] pwdsuits = socket.getSupportedCipherSuites(); //socket可以使用所有支持的加密套件 socket.setEnabledCipherSuites(pwdsuits); //默认就是true socket.setUseClientMode(true); SocketAddress address = new InetSocketAddress(host, port); socket.connect(address, 0); MyHandshakeCompletedListener listener = new MyHandshakeCompletedListener(); socket.addHandshakeCompletedListener(listener); } catch (Exception e) { e.printStackTrace(); logger.error("socket establish failed!"); } } public void request(){ try{ String encoding = p.getProperty("socketStreamEncoding"); DataOutputStream output = SocketIO.getDataOutput(socket); String user = "name"; byte[] bytes = user.getBytes(encoding); int length = bytes.length; int pwd = 123; output.write(length); output.write(bytes); output.write(pwd); DataInputStream input = SocketIO.getDataInput(socket); length = input.readShort(); bytes = new byte[length]; input.read(bytes); logger.info("request result:"+new String(bytes,encoding)); }catch(Exception e){ e.printStackTrace(); logger.error("request error"); }finally{ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args){ Client client = new Client(); client.request(); } }
服务端认证:
package com.test.server.auth; import java.io.FileInputStream; import java.security.KeyStore; import java.util.Properties; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import com.test.server.config.Configuration; public class Auth { private static SSLContext sslContext; public static SSLContext getSSLContext() throws Exception{ Properties p = Configuration.getConfig(); String protocol = p.getProperty("protocol"); String serverCer = p.getProperty("serverCer"); String serverCerPwd = p.getProperty("serverCerPwd"); String serverKeyPwd = p.getProperty("serverKeyPwd"); //Key Stroe KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream(serverCer), serverCerPwd.toCharArray()); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); keyManagerFactory.init(keyStore, serverKeyPwd.toCharArray()); KeyManager[] kms = keyManagerFactory.getKeyManagers(); TrustManager[] tms = null; if(Configuration.getConfig().getProperty("authority").equals("2")){ String serverTrustCer = p.getProperty("serverTrustCer"); String serverTrustCerPwd = p.getProperty("serverTrustCerPwd"); //Trust Key Store keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream(serverTrustCer), serverTrustCerPwd.toCharArray()); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); trustManagerFactory.init(keyStore); tms = trustManagerFactory.getTrustManagers(); } sslContext = SSLContext.getInstance(protocol); sslContext.init(kms, tms, null); return sslContext; } }
服务端主程序:
package com.test.server; import java.io.IOException; import java.net.InetSocketAddress; import java.util.Properties; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; import org.apache.log4j.Logger; import com.test.server.auth.Auth; import com.test.server.business.Job; import com.test.server.config.Configuration; public class Server { static Logger logger = Logger.getLogger(Server.class); private SSLContext sslContext; private SSLServerSocketFactory sslServerSocketFactory; private SSLServerSocket sslServerSocket; private final Executor executor; public Server() throws Exception{ Properties p = Configuration.getConfig(); Integer serverListenPort = Integer.valueOf(p.getProperty("serverListenPort")); Integer serverThreadPoolSize = Integer.valueOf(p.getProperty("serverThreadPoolSize")); Integer serverRequestQueueSize = Integer.valueOf(p.getProperty("serverRequestQueueSize")); Integer authority = Integer.valueOf(p.getProperty("authority")); executor = Executors.newFixedThreadPool(serverThreadPoolSize); sslContext = Auth.getSSLContext(); sslServerSocketFactory = sslContext.getServerSocketFactory(); //只是创建一个TCP连接,SSL handshake还没开始 //客户端或服务端第一次试图获取socket输入流或输出流时, //SSL handshake才会开始 sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(); String[] pwdsuits = sslServerSocket.getSupportedCipherSuites(); sslServerSocket.setEnabledCipherSuites(pwdsuits); //默认是client mode,必须在握手开始之前调用 sslServerSocket.setUseClientMode(false); if(authority.intValue() == 2){ //只有设置为server mode,该配置才生效 //如果客户端不提供其证书,通信将会结束 sslServerSocket.setNeedClientAuth(true); }else{ //只有设置为server mode,该配置才生效 //即使客户端不提供其证书,通信也将继续 sslServerSocket.setWantClientAuth(true); } sslServerSocket.setReuseAddress(true); sslServerSocket.setReceiveBufferSize(128*1024); sslServerSocket.setPerformancePreferences(3, 2, 1); sslServerSocket.bind(new InetSocketAddress(serverListenPort),serverRequestQueueSize); logger.info("Server start up!"); logger.info("server port is:"+serverListenPort); } private void service(){ while(true){ SSLSocket socket = null; try{ logger.debug("Wait for client request!"); socket = (SSLSocket)sslServerSocket.accept(); logger.debug("Get client request!"); Runnable job = new Job(socket); executor.execute(job); }catch(Exception e){ logger.error("server run exception"); try { socket.close(); } catch (IOException e1) { e1.printStackTrace(); } } } } public static void main(String[] args) { Server server; try{ server = new Server(); server.service(); }catch(Exception e){ e.printStackTrace(); logger.error("server socket establish error!"); } } }
服务端业务执行线程:
package com.test.server.business; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.util.Properties; import org.apache.log4j.Logger; import com.test.server.config.Configuration; import com.test.tools.SocketIO; public class Job implements Runnable { static Logger logger = Logger.getLogger(Job.class); private Socket socket; public Job(Socket socket){ this.socket = socket; } public void run() { Properties p = Configuration.getConfig(); String encoding = p.getProperty("socketStreamEncoding"); DataInputStream input = null; DataOutputStream output = null; try{ input = SocketIO.getDataInput(socket); int length = input.read(); byte[] bytes = new byte[length]; input.read(bytes); String user = new String(bytes,encoding).trim(); int pwd = input.read(); String result = null; if(null != user && !user.equals("") && user.equals("name") && pwd == 123){ result = "login success"; }else{ result = "login failed"; } logger.info("request user:"+user); logger.info("request pwd:"+pwd); output = SocketIO.getDataOutput(socket); bytes = result.getBytes(encoding); length = (short)bytes.length; output.writeShort(length); output.write(bytes); logger.info("response info:"+result); }catch(Exception e){ e.printStackTrace(); logger.error("business thread run exception"); }finally{ try { socket.close(); } catch (IOException e) { e.printStackTrace(); logger.error("server socket close error"); } } } }
配置文件类:
package com.test.server.config; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.Properties; import org.apache.log4j.Logger; public class Configuration { private static Properties config; static Logger logger = Logger.getLogger(Configuration.class); public static Properties getConfig(){ try{ if(null == config){ File configFile = new File("./conf/conf.properties"); if(configFile.exists() && configFile.isFile() && configFile.canRead()){ InputStream input = new FileInputStream(configFile); config = new Properties(); config.load(input); } } }catch(Exception e){ //default set config = new Properties(); config.setProperty("protocol", "TLSV1"); config.setProperty("serverCer", "./certificate/server.jks"); config.setProperty("serverCerPwd", "1234sp"); config.setProperty("serverKeyPwd", "1234kp"); config.setProperty("serverTrustCer", "./certificate/serverTrust.jks"); config.setProperty("serverTrustCerPwd", "1234sp"); config.setProperty("clientCer", "./certificate/client.jks"); config.setProperty("clientCerPwd", "1234sp"); config.setProperty("clientKeyPwd", "1234kp"); config.setProperty("clientTrustCer", "./certificate/clientTrust.jks"); config.setProperty("clientTrustCerPwd", "1234sp"); config.setProperty("serverListenPort", "10000"); config.setProperty("serverThreadPoolSize", "5"); config.setProperty("serverRequestQueueSize", "10"); config.setProperty("socketStreamEncoding", "UTF-8"); } return config; } }
接口工具类:
package com.test.tools; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; public class SocketIO{ public static DataInputStream getDataInput(Socket socket) throws IOException{ DataInputStream input = new DataInputStream(socket.getInputStream()); return input; } public static DataOutputStream getDataOutput(Socket socket) throws IOException{ DataOutputStream out = new DataOutputStream(socket.getOutputStream()); return out; } }
握手工具类:
package com.test.tools; import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; public class MyHandshakeCompletedListener implements HandshakeCompletedListener { public void handshakeCompleted(HandshakeCompletedEvent arg0) { System.out.println("Handshake finished successfully"); } }
配置文件:
#1:单向认证,只有服务器端需证明其身份 #2:双向认证,服务器端和客户端都需证明其身份 authority=2 #通信协议 protocol=TLSV1 #服务端证书信息 serverCer=./certificate/server.jks #keystore的storepass serverCerPwd=1234sp #keystore的keypass serverKeyPwd=1234kp #服务端证书信息 serverTrustCer=./certificate/serverTrust.jks serverTrustCerPwd=1234sp #客户端证书信息 clientCer=./certificate/client.jks clientCerPwd=1234sp clientKeyPwd=1234kp clientTrustCer=./certificate/clientTrust.jks clientTrustCerPwd=1234sp #服务器监听端口,注意root权限 serverListenPort=10000 #服务器线程池线程数(2*核数+1) serverThreadPoolSize=5 #服务器Socket请求队列长度 serverRequestQueueSize=10 #字节流编码 socketStreamEncoding=utf-8
日志文件:
log4j.rootLogger=debug,logOutput,fileLogOutput log console out put log4j.appender.logOutput=org.apache.log4j.ConsoleAppender log4j.appender.logOutput.layout=org.apache.log4j.PatternLayout log4j.appender.logOutput.layout.ConversionPattern=%p%d{[yy-MM-dd HH:mm:ss]}[%c] -> %m%n #log file out put log4j.appender.fileLogOutput=org.apache.log4j.RollingFileAppender log4j.appender.fileLogOutput.File=./log/server.log log4j.appender.fileLogOutput.MaxFileSize=1000KB log4j.appender.fileLogOutput.MaxBackupIndex=3 log4j.appender.fileLogOutput.layout=org.apache.log4j.PatternLayout log4j.appender.fileLogOutput.layout.ConversionPattern=%p%d{[yy-MM-dd HH:mm:ss]}[%c] -> %m%n
运行后的结果:
客户端:
Handshake finished successfully INFO[15-02-03 09:53:49][com.test.client.Client] -> request result:login success
服务端:
INFO[15-02-03 09:53:45][com.test.server.Server] -> Server start up! INFO[15-02-03 09:53:45][com.test.server.Server] -> server port is:10000 DEBUG[15-02-03 09:53:45][com.test.server.Server] -> Wait for client request! DEBUG[15-02-03 09:53:48][com.test.server.Server] -> Get client request! DEBUG[15-02-03 09:53:48][com.test.server.Server] -> Wait for client request! INFO[15-02-03 09:53:49][com.test.server.business.Job] -> request user:name INFO[15-02-03 09:53:49][com.test.server.business.Job] -> request pwd:123 INFO[15-02-03 09:53:49][com.test.server.business.Job] -> response info:login success
PS:
1,不能随意的使用close()方法关闭socket输入输出流,使用close()方法关闭socket输入输出流会导致socket本身被关闭
2,字符串必须按照指定的编码转换为字节数组,字节数组也必须通过相同的编码转换为字符串,否则将会出现乱码
String[] pwdsuits = socket.getSupportedCipherSuites(); socket.setEnabledCipherSuites(pwdsuits);
加密套件(ciphersuits)包括一组加密参数,这些参数指定了加密算法、密钥长度等加密等信息。
[SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, ...