最近在項目中用到了WebSocket通信的內(nèi)容
第一版的實現(xiàn)方案:spring-websocket + nv-websocket-client
第一版的模式已經(jīng)正常得到應(yīng)用土至,但有個問題,就是傳輸過程沒有加密猾昆,完全明文的方式陶因,這在當(dāng)前的時代不合時宜,所以考慮對通信過程進(jìn)行加密傳輸垂蜗。
第二版的實現(xiàn)方案:Java-WebSocket
這是一個Java的WebSocket開發(fā)組件楷扬,支持整合到后端、客戶端贴见,支持SSL/TLS加密傳輸協(xié)議烘苹。由于我目前的項目主要應(yīng)用場景在局域網(wǎng)內(nèi),所以對于常規(guī)的CA簽名證書部署并不合適蝇刀,我就用自簽名的形式進(jìn)行加密傳輸檢驗螟加。
話不多說,直接開干吞琐。
一捆探、準(zhǔn)備工作
準(zhǔn)備自簽名證書
keytool -genkey -validity 3650 -keyalg RSA -sigalg SHA256withRSA -keystore "keystore.jks" -storepass "storepassword" -keypass "keypassword" -alias "default" -dname "CN=127.0.0.1, OU=MyOrgUnit, O=MyOrg, L=MyCity, S=MyRegion, C=MyCountry"
這里要特別注意 -keyalg RSA -sigalg SHA256withRSA 這兩個屬性,后面要將jks轉(zhuǎn)換成bks才能在Android平臺使用站粟,沒有這兩個屬性會導(dǎo)致handshake的時候報“Possible no handshake recieved!”黍图,說明簽名校驗不通過。
github上的Issue說明
將jks簽名文件轉(zhuǎn)換成bks奴烙,因為Android只能讀取bks格式的簽名秘鑰助被,轉(zhuǎn)換方法有好幾種,這里介紹一個命令行方式的
轉(zhuǎn)換方法
二切诀、程序?qū)崿F(xiàn)
java后臺
WebSocketImpl.DEBUG = true;
ChatServer chatServer = new ChatServer(9089); // 這里可以自定義端口揩环,默認(rèn)80
logger.info("charServer port:" + chatServer.getPort());
// load up the key store
String STORETYPE = "JKS";
String KEYSTORE = "/Users/randysu/keystore.jks";
String STOREPASSWORD = "storepassword"; // 之前在制作keystore.jks時指定的store密碼
String KEYPASSWORD = "keypassword"; // 之前在制作keystore.jks時指定的key密碼
KeyStore ks = KeyStore.getInstance( STORETYPE );
File kf = new File( KEYSTORE );
ks.load( new FileInputStream( kf ), STOREPASSWORD.toCharArray() );
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init( ks, KEYPASSWORD.toCharArray() );
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init( ks );
SSLContext sslContext = null;
sslContext = SSLContext.getInstance( "TLS" );
sslContext.init( kmf.getKeyManagers(), tmf.getTrustManagers(), null );
// DefaultSSLWebSocketServerFactory 這里用的是默認(rèn)的加密倉庫,默認(rèn)允許加載所有的簽名方式幅虑,也可以自定義加密倉庫丰滑,移除不能校驗通過的簽名方式
// SSLEngine engine = sslContext.createSSLEngine();
// List<String> ciphers = new ArrayList<String>( Arrays.asList(engine.getEnabledCipherSuites()));
// ciphers.remove("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
// List<String> protocols = new ArrayList<String>( Arrays.asList(engine.getEnabledProtocols()));
// protocols.remove("SSLv3");
// CustomSSLWebSocketServerFactory factory = new CustomSSLWebSocketServerFactory(sslContext, protocols.toArray(new String[]{}), ciphers.toArray(new String[]{}));
// chatserver.setWebSocketFactory(factory);
chatServer.setWebSocketFactory( new DefaultSSLWebSocketServerFactory( sslContext ) );
chatServer.start();
Android端
// load up the key store
String STORETYPE = "bks";
String KEYSTORE = Environment.getExternalStorageDirectory().getPath() + File.separator + "keystore.bks";
String STOREPASSWORD = "storepassword";
String KEYPASSWORD = "keypassword";
KeyStore ks = KeyStore.getInstance(STORETYPE);
File kf = new File(KEYSTORE);
ks.load(new FileInputStream(kf), STOREPASSWORD.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(ks, KEYPASSWORD.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
SSLContext sslContext = null;
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLSocketFactory factory = sslContext.getSocketFactory();
URI socketUri = new URI("wss://192.168.1.230:9089/server");
SSLWebSocketClient socketClient = new SSLWebSocketClient(socketUri, mWsListener);
socketClient.setSocket(factory.createSocket());
socketClient.connect();
// socketClient.connectBlocking(); // 阻塞當(dāng)前線程直至后臺返回連接成功或失敗
SSLWebSocketClient 繼承 WebSocketClient(Java-WebSocketde的對象),mWsListener是一個連接狀態(tài)的回調(diào)接口倒庵,在里面處理連接狀態(tài)褒墨。
mWsListener = new WsListener() {
@Override
public void onConnected(ServerHandshake handshakedata) {
//連接成功
Logger.i("websoeket opened connection");
}
@Override
public void onTextMessage(String message) {
//服務(wù)端消息來了
Logger.i("websoeket received:" + message);
}
@Override
public void onDisconnected(int code, String reason, boolean remote) {
//連接斷開,remote判定是客戶端斷開還是服務(wù)端斷開
Logger.i("websoeket Connection closed by " + (remote ? "remote server" : "us"));
}
@Override
public void onConnectError(Exception ex) {
// 連接錯誤
Logger.i("websoeket error:" + ex);
}
};
}
java后臺向Android發(fā)送消息
WebSocket對象的send(String str)方法
Android端向java后臺發(fā)送消息
SSLWebSocketClient的send(String str)方法
這里要注意一點(diǎn)擎宝,Android定義的證書格式是“ X509”郁妈,Java后臺定義的證書格式是“ SunX509”,如果Android定義“SunX509”會報錯绍申,同理也不能在Java后臺定義“X509”噩咪。
三、實測結(jié)果
推薦一本書《HTTPS權(quán)威指南》
Done!