前言
以前在寫「一套簡單的web即時通信」,寫到第三版的時候沒什麼思路,正好微信公衆號看到一篇講API交互加密,因而就本身搞了一套AES與RSA混合加密,無心中產生應用在WebSocket想法,好在思路都差很少,稍微改動一下就能實現,特地寫這篇博客記錄下來html
WebSocket是HTML5 開始提供的一種瀏覽器與服務器進行全雙工通信的網絡技術,屬於應用層協議。它基於 TCP 傳輸協議,並複用 HTTP 的握手通道。前端
一、創建鏈接,客戶端經過 HTTP 請求與服務端協商升級協議。協議升級完成後,後續的數據交換則遵守 WebSocket 的協議。java
二、數據交換,創建鏈接後,後續的操做經過TCP協議以數據幀的格式傳輸交換。web
經過TCP進行數據交換,不像http請求,websocket數據交換在瀏覽器上使用開發者工具(F12)是看不到數據,但使用抓包軟件仍然能夠獲取到這些TCP傳輸數據,例如Charles和Fiddler等,以明文的方式直接傳輸很容易被第三方監聽,所以,我以爲是有必要的後端
前面咱們實現了一套AES與RSA混合加密(詳情請戳:先後端API交互數據加密——AES與RSA混合加密完整實例),咱們如今用它實現一下WebSocket數據加密瀏覽器
思路、代碼
工具類咱們直接用以前API加密那一套就行,操做也與以前的API加密相似,發送前加密、收到數據後解密再交給業務處理,有個地方要注意的是,咱們在進行消息轉發時,要用的是接收方的前端公鑰進行加密服務器
按照咱們目前的規則,項目啓動後生成後端密鑰對,訪問登陸頁面時響應後端公鑰給前端,前端保存到後端RSA公鑰並存到sessionStorage,每一個頁面都引入頭部head.html(使用thymeleaf的<script th:replace="head::static"></script>),在head裏面獲取前端RSA公鑰密碼、AES的key,並放到window,這些都跟以前的同樣,不須要改變,須要改動的是下面這些:微信
創建WebSocket鏈接時,將當前用戶的前端公鑰發送到後端,後端進行Map保存websocket
//判斷當前瀏覽器是否支持WebSocket if ('WebSocket' in window) { //由於是url的方式傳值,公鑰中的/須要進行轉換一下,傳到後端再轉回來(PS:由於生成的公鑰裏是不存在","的,因此這裏轉成逗號) websocket = new WebSocket("ws://localhost:10086/websocket/" + userId + "/" + window.jsPublicKey.replace(/\//g,",")); } else { console.error("不支持WebSocket"); }
/** * WebSocket服務 */ @Component @ServerEndpoint(value = "/websocket/{userId}/{publicKey}", configurator = MyEndpointConfigure.class) public class WebSocketServer { //省略其餘代碼 /** * 登陸用戶的前端公鑰Map集合(其實應該放在Redis) */ private static Map<Session, String> loginPublicKeyList = new HashMap<Session, String>(); /** * 鏈接創建成功調用的方法 */ @OnOpen public void onOpen(Session session, @PathParam("userId") String userId, @PathParam("publicKey") String publicKey) { //省略其餘代碼 //設置前端公鑰,由於是url的方式傳值,公鑰中的/須要進行轉換一下,傳到後端再轉回來,而後將每一個用戶的前端公鑰存儲起來 loginPublicKeyList.put(session,publicKey.replaceAll(",", "/")); } }
前端發送前加密網絡
//發送消息 function send(but) { //業務操做不變,省略代碼 //先加密 let aesKey = aesUtil.genKey(); let data = { data: aesUtil.encrypt(JSON.stringify({ "type": "1", "toUser": {"userId": toUserId}, "fromUser": {"userId": fromUserId}, "message": message, "date": nowTime }), aesKey),//AES加密後的數據 aesKey: rsaUtil.encrypt(aesKey, sessionStorage.getItem('javaPublicKey')),//後端RSA公鑰加密後的AES的key publicKey: window.jsPublicKey//前端公鑰 }; websocket.send(JSON.stringify(data)); //業務操做不變,省略代碼 }
後端收到後先解密
/** * 服務器接收到客戶端消息時調用的方法 */ @OnMessage public void onMessage(String message, Session session) { try { //jackson ObjectMapper mapper = new ObjectMapper(); //jackson 序列化和反序列化 date處理 mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); //JSON字符串轉 HashMap HashMap map = mapper.readValue(message, HashMap.class); //先解密 String data = (String) map.get("data"); String aesKey = (String) map.get("aesKey"); //後端私鑰解密的到AES的key byte[] plaintext = RsaUtil.decryptByPrivateKey(Base64.decodeBase64(aesKey), RsaUtil.getPrivateKey()); aesKey = new String(plaintext); //RSA解密出來字符串多一對雙引號 aesKey = aesKey.substring(1, aesKey.length() - 1); //AES解密獲得明文data數據 String decrypt = AesUtil.decrypt(data, aesKey); //JSON字符串轉 HashMap HashMap hashMap = mapper.readValue(decrypt, HashMap.class); //獲得hashMap,下面的業務操做跟前面的同樣,這裏就不貼出來了 } catch (Exception e) { e.printStackTrace(); } }
後端發送以前先加密,這裏要用消息接收方的前端公鑰進行加密
/** * 封裝一個send方法,發送消息到前端 */ private void send(Session session, String message) { try { //發送前加密 //每次響應以前隨機獲取AES的key,加密data數據 String key = AesUtil.getKey(); String data = AesUtil.encrypt(message, key); //用前端的公鑰來解密AES的key,並轉成Base64,注意:這裏須要用接收方的前端公鑰進行加密,從loginPublicKeyList集合獲取 String aesKey = Base64.encodeBase64String(RsaUtil.encryptByPublicKey(key.getBytes(), loginPublicKeyList.get(session))); //發送過去的是AES加密後的data,跟RSA加密後的aesKey session.getBasicRemote().sendText("{\"data\":\"" + data + "\",\"aesKey\":\"" + aesKey + "\"}"); } catch (Exception e) { e.printStackTrace(); } }
前端收到消息後先解密
//接收到消息的回調方法 websocket.onmessage = function (event) { let data = eval("(" + event.data + ")"); //先解密 let msgObj = aesUtil.decrypt(data.data, rsaUtil.decrypt(data.aesKey, window.jsPrivateKey)); //業務操做不變,省略代碼 };
效果演示
按照程序流程:
前端加密前、加密後
後端解密前、解密後
後端加密前、加密後
前端解密前、解密後
完整頁面演示:
好友上線在線系統通知沒有問題
聊天沒有問題
後記
WebSocket的加密就先記錄到這裏,其餘的後面再補充
原文出處:https://www.cnblogs.com/huanzi-qch/p/11010153.html