• 一套简单的web即时通讯——第三版


      前言

      接上版,本次版本做了如下优化:

      1、新增同意、拒绝添加好友后做线上提示;

      2、新增好友分组,使用工具生成后台API,新增好友分组功能,主要功能有:添加分组、重命名分组名称、删除分组

      3、新增好友管理,主要功能:删除好友(下个版本再实现功能)、移动好友至其他分组

      4、添加好友时有验证信息、好友备注、好友分组 

      5、AIP接口、WebSocket通道的前后端交互采用AES与RSA混合加密,防抓包监听,加、解密操作后并不影响业务,AIP接口、WebSocket通道的前后端交互正常

      优化细节

      1、新增同意、拒绝添加好友后做线上提示;

      

      2、新增好友分组,使用工具生成后台API,新增好友分组功能,主要功能有:添加分组、重命名分组名称、删除分组

     

      没有分组的默认在列表前面追加,分组名称后面展示对应好友数以及在线好友数量

      添加分组、重命名分组名称、删除分组

      

      3、新增好友管理,主要功能:删除好友(下个版本再实现功能)、移动好友至其他分组

      移动好友至其他分组

      

      4、添加好友时有验证信息、好友备注、好友分组 

       先登录两个还不是好友的人,各种新增一个好友分组

     

      A向B发起好友申请

      同意好友申请

       拒绝好友申请

      5、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混合加密,单独写一篇博客

  • 相关阅读:
    Dubbo-admin管理平台搭建
    Zabbix监控Zookeeper健康状况
    Redis-stat 的安装与使用
    Jenkins配置基于角色的项目权限管理
    云计算的三种服务模式:IaaS,PaaS和SaaS
    PRC远程过程调用
    解决linux中ssh登录Warning:Permanently added (RSA) to the list of known hosts
    Maven仓库—Nexus环境搭建及使用
    SecureCRT使用本地公钥 SSH 免密码登录Linux
    Linux内存管理机制
  • 原文地址:https://www.cnblogs.com/huanzi-qch/p/10895703.html
Copyright © 2020-2023  润新知