一套簡單的web即時通信——第三版

  前言

  接上版,本次版本作了以下優化:html

  一、新增贊成、拒絕添加好友後作線上提示;前端

  二、新增好友分組,使用工具生成後臺API,新增好友分組功能,主要功能有:添加分組、重命名分組名稱、刪除分組java

  三、新增好友管理,主要功能:刪除好友(下個版本再實現功能)、移動好友至其餘分組web

  四、添加好友時有驗證信息、好友備註、好友分組 後端

  五、AIP接口、WebSocket通道的先後端交互採用AES與RSA混合加密,防抓包監聽,加、解密操做後並不影響業務,AIP接口、WebSocket通道的先後端交互正常安全

 

  優化細節

  一、新增贊成、拒絕添加好友後作線上提示;服務器

  

  二、新增好友分組,使用工具生成後臺API,新增好友分組功能,主要功能有:添加分組、重命名分組名稱、刪除分組websocket

 

  沒有分組的默認在列表前面追加,分組名稱後面展現對應好友數以及在線好友數量session

  添加分組、重命名分組名稱、刪除分組app

  

 

  三、新增好友管理,主要功能:刪除好友(下個版本再實現功能)、移動好友至其餘分組

  移動好友至其餘分組

  

  四、添加好友時有驗證信息、好友備註、好友分組 

   先登陸兩個還不是好友的人,各類新增一個好友分組

 

  A向B發起好友申請

  贊成好友申請

   拒絕好友申請

  五、AIP接口、WebSocket通道的先後端交互採用AES與RSA混合加密,防抓包監聽,加、解密操做後並不影響業務,AIP接口、WebSocket通道的先後端交互正常

  API交互,關於先後端API安全交互,我前段時間實現了一套AES與RSA混合加密,詳情請戳:先後端API交互數據加密——AES與RSA混合加密完整實例

  WebSocket聊天,webSocket的加、解密與AIP的加、解密原理同樣,發送前加密、收到數據後解密再交給業務處理,有個地方要注意的是,咱們在進行消息轉發時,要用的是接收方的前端公鑰進行加密

  創建WebSocket鏈接時,將當前用戶的前端公鑰發送到後端,後端進行Map保存(只貼出關鍵代碼)

    //由於是url的方式傳值,公鑰中的/須要進行轉換一下,傳到後端再轉回來(PS:由於生成的公鑰裏是不存在","的,因此這裏轉成逗號)
    websocket = new WebSocket("ws://localhost:10086/websocket/" + userId + "/" + window.jsPublicKey.replace(/\//g,","));
/**
 * 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的AES與RSA混合加密,單獨寫一篇博客

相關文章
相關標籤/搜索