基于netty框架不使用SSL证书,实现websocket数据加密传输

news2024/12/27 10:02:15

文章目录

  • 简介
  • 实现方式
  • 主要代码
  • 调用方法

  • 1、简介

  • 2、实现方式

  • 3、服务端主要代码

  • 4、客户端主要代码

  • 5、调用方式

简介

  • 为什么不使用SSL证书?
    1、服务器运行在专网环境,不能访问互联网。证书有有效期,CA机构规定,证书有效期最多2年。在客户的专网环境里更新和维护证书就会增加运营成本。
  • 实现逻辑?
    参照SSL的实现逻辑,与SSL的区别就是SSL的公钥是通过证书下发的,这里为了免去证书维护的麻烦,公钥直接下发给客户端。SSL处理流程如下:image.png
    详情可参见本人另一篇博文:HTTPS请求过程

实现方式

  • 使用装饰者模式对原有的websocket模块进行功能扩展。
  • 服务端基于 YeautyYE 基于Netty的开源项目netty-websocket-spring-boot-starter(轻量级、高性能的WebSocket框架)在应用层封装加密过程,客户端接入时进行密钥交换。
    重写onOpen、onClose方法,对客户端连接和断开进行处理。重写消息接收和消息发送方法,对消息进行加解密和过滤。
  • 客户端基于Java-WebSocket,重写消息接收和消息发送方法,实现数据加解密。

主要代码

  • 服务端主要代码:
/**
 * 安全的websocket服务
 */
public abstract class SercurityWebsocketService {

    public static final String CHAR_SET = "UTF-8";
    //存放RSE密钥对
    static Map<String, String> keyPairMap = null;
    //存放客户端发过来的AES密钥,key为client的session key
    static Map<String, String> clientKeysMap = new ConcurrentHashMap<>();

    static {
        try {
            keyPairMap = RSACoder.genKeyPair();
            System.out.println("公钥:" + keyPairMap.get(RSACoder.PUBLIC_KEY));
            System.out.println("私钥:" + keyPairMap.get(RSACoder.PRIVATE_KEY));
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("密钥对初始化失败:" + e.getMessage());
        }
    }


    //服务启动,生成RSA密钥对,保存在本地。
    //客户端连接时,给客户端发送公开密钥+clientId(sessionId)。
    //接收客户端发送的随机码
    //解密随机码,并且使用sessionId作为client_id,保存。
    @OnOpen
    public void OnOpen(Session session, HttpHeaders headers) throws IOException {
        //1、直接把密钥发给客户端。
        SecurityMessageEntity entity = new SecurityMessageEntity();
        entity.setMsgType(SecurityMessageEntity.MSG_TYPE_PUBLIC_KEY);
        entity.setSercurityContent(keyPairMap.get(RSACoder.PUBLIC_KEY));
        send(session,keyPairMap.get(RSACoder.PUBLIC_KEY),
                SecurityMessageEntity.SECURITY_TYPE_RSE,
                SecurityMessageEntity.MSG_TYPE_PUBLIC_KEY);

        //调用子类的实现
        this.onOpen(session, headers);
    }

    @OnClose
    public void OnClose(Session session) throws IOException {
        //客户端关闭,移除clientKey
        clientKeysMap.remove(session.id().asLongText());
        System.out.println("移除clientKey:" + session.id().asLongText());
        //调用子类的实现
        this.onClose(session);
    }

    @OnError
    public void OnError(Session session, Throwable throwable) {
        clientKeysMap.remove(session.id().asLongText());
        throwable.printStackTrace();
        onError(session, throwable);
    }

    @OnMessage
    public void OnMessage(Session session, String message) {
        try {
            String jsonMessage = dencryptFromBase64String(message);
            onMessageHandle(session, jsonMessage);
        } catch (Exception e) {
            session.sendText("密钥解析失败:" + e.getMessage());
        }
    }

    /**
     * websocket处理接收到的消息,解析后得到消息体内容,调用抽象方法OnMessage。
     * 1、验证是否有securityType,如果有securityType,按照密文类型,进行解密。
     * 2、如果没有securityType,当成明文处理
     *
     * @param session
     * @param msg
     */
    private void onMessageHandle(Session session, String msg) throws Exception {
        JSONObject jsonObject = JSON.parseObject(msg);
        if (jsonObject.containsKey("securityType")) {

            SecurityMessageEntity keyEntity = JSON.parseObject(msg, SecurityMessageEntity.class);
            switch (keyEntity.getSecurityType()) {
                case SecurityMessageEntity.SECURITY_TYPE_RSE:
                    //RES加密方式,为客户端传过来的client key,用于aes加解密。
                    String aesKey = RSACoder.decrypt(keyEntity.getSercurityContent(), keyPairMap.get(RSACoder.PRIVATE_KEY));
                    clientKeysMap.put(session.id().asLongText(), aesKey);
                    System.out.println("客户端id:" + session.id().asLongText());
                    System.out.println("aes key:" + aesKey);
                    break;
                case SecurityMessageEntity.SECURITY_TYPE_AES:
                    //AES加密方式。
                    String key = clientKeysMap.get(session.id().asLongText());
                    if (StringUtils.isEmpty(key)) {
                        System.out.println("解析消息失败,找不到对应的clientKey");
                        session.sendText("解析消息失败,找不到对应的clientKey");
                    } else {
                        //使用AES进行解密
                        String decryptMessage = AesHelper.decrypt(keyEntity.getSercurityContent(), key);
                        this.onMessage(session, decryptMessage);
                    }
                default:
                    break;
            }
        } else {
            this.onMessage(session, msg);
        }
    }


    /**
     * 解析客户端穿过来的数据
     * 1、把客户端传过来的base64字符串进行解密,得到json字符串,
     * 2、json字符串转换为key
     * 3、把加密的密钥进行解密。
     * 4、返回
     *
     * @param str
     * @return
     * @throws Exception
     */
    public static String dencryptFromBase64String(String str) throws Exception {
        byte[] bytes = Base64.decodeBase64(str);
        String jsonString = new String(bytes, CHAR_SET);
        return jsonString;
    }

    /**
     * 发送消息
     *
     * @param session
     * @param text
     * @throws NotYetConnectedException
     */
    public void send(Session session, String text) throws NotYetConnectedException {
        try {
            String key = clientKeysMap.get(session.id().asLongText());
            String encryptMessage = AesHelper.encrypt(text, key);
            send(session, encryptMessage, SecurityMessageEntity.SECURITY_TYPE_AES, null);

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            System.out.println("消息发送失败:" + e.getMessage());
        }
    }

    private void send(Session session, String text, String securityType, String msgType) throws UnsupportedEncodingException {
        SecurityMessageEntity entity = new SecurityMessageEntity();
        entity.setSecurityType(securityType);
        entity.setMsgType(msgType);
        entity.setSercurityContent(text);
        String jsonMessage = JSONObject.toJSONString(entity);
        String msgContent = Base64.encodeBase64String(jsonMessage.getBytes(CHAR_SET));
        session.sendText(msgContent);
    }

    /**收到消息回调方法
     * @param session 会话session
     * @param message
     */
    abstract void onMessage(Session session, String message);

    /**
     * 打开连接回调方法
     * @param session 会话session
     * @param headers
     */
    abstract void onOpen(Session session, HttpHeaders headers);

    /**连接出错回调方法
     * @param session 会话session
     * @param throwable
     */
    abstract void onError(Session session, Throwable throwable);

    /**连接关闭回调方法
     * @param session 会话session
     * @throws IOException
     */
    abstract void onClose(Session session) throws IOException;
}

  • 客户端主要代码:
public abstract class SercurityWebSocketClient extends WebSocketClient {
    private static String CHAR_SET = "UTF-8";
    private static String clientKey;

    public SercurityWebSocketClient(String url) throws URISyntaxException {
        super(new URI(url));
    }

    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        System.out.println("握手...");
        onConnect(serverHandshake);
    }

    @Override
    public void onMessage(String s) {
        try {
            onMessageHandle(s);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息解析出错:" + e.getMessage());
        }
    }

    @Override
    public void onClose(int i, String s, boolean b) {
        onDisconnect(i,s,b);
    }

    @Override
    public void onError(Exception e) {
        onException(e);
    }

    /**
     * 消息处理
     *
     * @param msg
     * @throws Exception
     */
    private void onMessageHandle(String msg) throws Exception {
        //1、使用base64解码
        String jsonMsg = dencryptFromBase64String(msg);
        //2、验证是否是Public key。
        JSONObject jsonObject = JSONObject.parseObject(jsonMsg);
        if (SecurityMessageEntity.MSG_TYPE_PUBLIC_KEY.equals(jsonObject.getString("msgType"))) {
            //说明是服务器发送过来的公钥。随机生成client_key,
            clientKey = createClientKey();
            String encrypt = RSACoder.encrypt(clientKey, jsonObject.getString("sercurityContent"));
            System.out.println(String.format("明文:%s,非对称加密结果:%s", clientKey, encrypt));

             send(encrypt,SecurityMessageEntity.SECURITY_TYPE_RSE,null);
        } else {
            //否则是普通加密的消息。使用本地的clientKey解密
            String jsonContent = AesHelper.decrypt(jsonObject.getString("sercurityContent"), clientKey);
            onReceiveMessage(jsonContent);
        }
    }

    /**
     * 创建客户端的密钥,并使用public key进行加密。
     *
     * @return
     */
    private String createClientKey() throws Exception {
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        return uuid;
    }


    /**
     * 把base64的字符串解析为json字符串
     *
     * @param str
     * @return
     * @throws Exception
     */
    public static String dencryptFromBase64String(String str) throws Exception {
        byte[] bytes = Base64.decodeBase64(str);
        String jsonString = new String(bytes, CHAR_SET);
        return jsonString;
    }

    /**重写发送方法,消息发送之前先进行加密
     * @param text
     * @throws NotYetConnectedException
     */
    @Override
    public void send(String text) throws NotYetConnectedException {
        try {
            String encryptMessage = AesHelper.encrypt(text, clientKey);
            send(encryptMessage, SecurityMessageEntity.SECURITY_TYPE_AES,null);

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            System.out.println("消息发送失败:" + e.getMessage());
        }
    }

    private void send(String text, String securityType,String msgType) throws UnsupportedEncodingException {
        SecurityMessageEntity entity = new SecurityMessageEntity();
        entity.setSecurityType(securityType);
        entity.setMsgType(msgType);
        entity.setSercurityContent(text);
        String jsonMessage = JSONObject.toJSONString(entity);
        String msgContent = Base64.encodeBase64String(jsonMessage.getBytes(CHAR_SET));
        super.send(msgContent);
    }


    /**收到消息
     * @param s
     */
    protected abstract void onReceiveMessage(String s);

    /**连接断开
     * @param i
     * @param s
     * @param b
     */
    protected abstract void onDisconnect(int i, String s, boolean b);

    /**连接异常
     * @param e
     */
    protected abstract void onException(Exception e);

    /**
     * 连接成功
     * @param serverHandshake
     */
    protected abstract void onConnect(ServerHandshake serverHandshake);
}

调用方法

跟原框架调用逻辑类似,自定义类继承服务端(SercurityWebsocketService)或者客户端(SercurityWebSocketClient)类,实现其中的抽象方法即可。发送消息调用super类中的send方法即可。

  • 服务端调用示例:
@ServerEndpoint(prefix = "netty-websocket",path = "/sercurity" )
@Component
public class BusinessServiceImpl extends SercurityWebsocketService {

    @Override
    void onMessage(Session session, String message) {
        System.out.println("接收到客户端消息:"+message);
        send(session,"您好,client,已经收到您的消息。");

    }

    @Override
    void onOpen(Session session, HttpHeaders headers) {
        System.out.println("调用子方法");
    }

    @Override
    void onError(Session session, Throwable throwable) {

    }

    @Override
    void onClose(Session session) throws IOException {

    }
}

  • 客户端调用示例:
        try {
            SercurityWebSocketClient socketClient=new SercurityWebSocketClient("ws://127.0.0.1:9002/sercurity") {
                @Override
                public void onReceiveMessage(String s) {

                }

                @Override
                protected void onDisconnect(int i, String s, boolean b) {

                }

                @Override
                protected void onException(Exception e) {

                }

                @Override
                protected void onConnect(ServerHandshake serverHandshake) {

                }
            };
            socketClient.connect();
            while (!socketClient.getReadyState().equals(WebSocket.READYSTATE.OPEN)) {
                System.out.println("还没有打开");
                Thread.sleep(2000);
            }
            System.out.println("建立websocket连接");
            socketClient.send("我是client");
            try {
                System.in.read();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/518112.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3【使用axios并封装axios请求】

第一步&#xff1a;安装axios npm install axios 第二步&#xff1a;编写请求文件 新建request.js 简单的axios封装&#xff0c;里面相关提示信息&#xff0c;自己可以引入element-plus去添加 /**axios封装* 请求拦截、相应拦截、错误统一处理*/ import axios from axios; i…

Linux入门篇-安装CentOS

一、先组织硬件 先把“买”一台空白的电脑&#xff0c;再进行操作系统的安装。 windows中&#xff0c;处理器&#xff1a; 本机 实际运行的时候能当多少个CPU来使呢&#xff1f;16个 处理器的数量是CPU的数量&#xff1b;&#xff08;本机上一个插槽&#xff0c;就只有一个CP…

同城跑腿能赚多少钱?“爱折腾”的创业者是否值得入局一试?

跑腿服务兴起的最大特点是节省时间和成本。现在城市发展越来越快&#xff0c;真的应该是“时间就是金钱”的道理。帮助人们排队购物、送外卖、送文件、买花、叫醒服务……“跑腿”在生活中无处不在。 同城跑腿系统的前景有多大&#xff1f; 一方面&#xff0c;外卖和网上购物…

深入学习MYSQL-使用触发器

触发器 每个表最多支持6个触发器&#xff0c;&#xff08;insert&#xff0c;update&#xff0c;delete&#xff09;之前和之后。 删除触发器 drop trigger trigger_name;insert 触发器  在INSERT触发器代码内&#xff0c;可引用一个名为NEW的虚拟表&#xff0c;访问被插入…

第一章--Java语言概述

一 Java知识脉络图 1.1 Java基础全程脉络图 1.2 本章专题与脉络 二 计算机的硬件与软件 2.1 计算机组成&#xff1a;硬件软件 2.2 CPU、内存与硬盘 CPU&#xff08;Central Processing Unit&#xff0c;中央处理器&#xff09; 人靠大脑思考&#xff0c;电脑靠CPU来运算、控…

【beego】bee工具安装使用

bee 工具是一个为了协助快速开发 beego 项目而创建的项目&#xff0c;通过 bee 您可以很容易的进行 beego 项目的创建、热编译、开发、测试、和部署。 1、安装 go get github.com/beego/bee2、配置bee.exe 运行完以上指令后&#xff0c;打开$GOPATH/bin &#xff0c;我的路径…

刷完这个笔记,17K不能再少了....

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;得准备面试了&#xff0c;又不知道从何下手&#xff01;为了帮大家节约时间&#xff0c;特意准备了一份面试相关的资料&#xff0c;内容非常的全面&#xff0c;真的可以好好补一补&#xff0c;希望大家在都能拿到理想…

2直接连接的网络与VLAN划分-2.1【实验】【计算机网络】

2直接连接的网络与VLAN划分【实验】【计算机网络】 前言推荐2直接连接的网络与VLAN划分2.1共享式以太网和交换式以太网实验目的实验内容及实验环境实验原理共享式以太网交换式以太网 实验过程搭建实验环境初始化序训练操作共享式以太网-操作交换式以太网查看共享式以太网冲突查…

java中ArrayList顺序表的常见操作

ArrayList常见操作 1.插入操作 尾插 e boolean add(E e) 将 e 插入到 index 位置&#xff08;指定位置插入&#xff09; void add(int index, E element) 尾插 c 中的元素(直接插入一个集合) boolean addAll(Collection<? extends E> c) 将c插入到index位置&#x…

java基础--(一)创建对象在内存中的理解

1、代码 package demo; public class Car {String color;int num;public Car() {}public void run(){System.out.println("color " color);System.out.println("num " num);}public Car(String color, int num) {this.color color;this.num num;} …

2022年美国大学生数学建模竞赛D题数据瘫痪? 使用我们的分析!解题全过程文档及程序

2022年美国大学生数学建模竞赛 D题数据瘫痪? 使用我们的分析! 原题再现&#xff1a; 背景   很多公司将数据视为战略资产&#xff0c;但却很难从中获益。妥善管理这一资源(数据)可以带来竞争优势。因此&#xff0c;公司需要建立一一个集成的数据和分析(D&A) 系统&…

网络中的数据传输模式有哪些-镭速

数据传输模式定义了两个通信设备之间信息流的方向&#xff0c;也称为数据通信或定向模式&#xff0c;同事也规定了信息在计算机网络中从一处流向另一处的方向。 在开放系统互连&#xff08;OSI&#xff09;层模型中&#xff0c;物理层专用于网络中的数据传输。它主要决定数据到…

微信小程序原生开发功能合集十四:登录健权及注册功能实现

本章实现微信自动登录及注册修改功能,包括匿名账号生成、权限自动检测、注册界面及注册流程的实现。   另外还提供小程序开发基础知识讲解课程,包括小程序开发基础知识、组件封装、常用接口组件使用及常用功能实现等内容,具体如下:    1. CSDN课程: https://edu.csdn…

1-SpringBoot工程打包后为何执行Java -Jar就能启动?

本文主要分享SpringBoot工程项目如何打包成一个可直接通过java -jar执行的jar&#xff0c;并且简单分析其启动步骤原理。 文章目录 1.SpringBoot如何打包成一个可执行jar&#xff1f;2.SpringBoot打包成的jar为何可以直接Java -jar执行&#xff1f;3.一窥SpringBoot初启动 1.S…

企业直播该如何做?硬件设备、网络环境、设备连接和观看权限等整个直播流程教程

这是一份面向直播新手的企业直播说明教程&#xff0c;字数较多&#xff0c;完整看完&#xff0c;可能会需要求10分钟&#xff0c;建议您可以【收藏】&#xff0c;如果本文章对您有帮助&#xff0c;就帮助【点个赞】吧~~~ 阿酷TONY / 2023-5-12 / 原创文章 / 长沙 / 文章…

今天的CRM系统,还仅仅是一套营销工具吗?

如今&#xff0c;数字化浪潮正席卷全球&#xff0c;数字经济已经成为全球经济未来发展的新动能和新方向。据IDC的预测显示&#xff0c;全球对数字转型的投资将以每年17.1%的复合速度增长&#xff0c;2023年这类投资预计将达到2.3万亿美元。而CRM系统&#xff08;客户关系管理系…

用友NC软件被locked1勒索病毒攻击加密的方式,服务器oracle数据库中了勒索病毒

用友NC软件是一款企业级管理软件&#xff0c;广泛应用于各行各业的集团企业中。然而&#xff0c;最近有关locked1勒索病毒对用友NC软件的攻击已经引起了广泛的关注和担忧。下面将为大家介绍一下有关locked1攻击加密用友NC软件的方式&#xff0c;并给出合理的解决方案。 首先&a…

Kafka 之生产者与消费者基础知识:基本配置、拦截器、序列化、分区器

一、配置 1. 必须要配置的参数&#xff1a; kafaf集群地址列表&#xff1a;理论上写一个节点地址&#xff0c;就相当于绑定了整个kafka集群了&#xff0c;但是建议多写几个&#xff0c;如果只写一个&#xff0c;万一宕机就麻烦了kafka消息的key和value要指定序列化方法kafka对…

2023年湖北省建设厅特种作业操作证报名条件是什么?

建筑施工特种作业人员是指在房屋建筑和市政工程施工活动中&#xff0c;从事可能对本人、他人及周围设备设施的安全造成重大危害作业的人员。建筑施工特种作业人员必须经建设主管部门考核合格&#xff0c;取得建筑施工特种作业人员操作资格证书&#xff08;以下简称“资格证书”…

零代码基础,一分钟教你快速搭建微信 ChatGPT 机器人

零代码基础,一分钟教你快速搭建微信 ChatGPT 机器人 1.注册 Railway 账号:2.部署3. 配置1)点击 Configure2)选择仓库地址3)点击安装4)配置信息如下图:5)部署:deploy6)部署完成:7)微信扫码登录8) 根据需要修改配置:最后总结:效果预览:致谢开源项目:本教程收集于…