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

这篇具有很好参考价值的文章主要介绍了基于netty框架不使用SSL证书,实现websocket数据加密传输。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  • 1、简介

  • 2、实现方式

  • 3、服务端主要代码

  • 4、客户端主要代码

  • 5、调用方式

简介

  • 为什么不使用SSL证书?
    1、服务器运行在专网环境,不能访问互联网。证书有有效期,CA机构规定,证书有效期最多2年。在客户的专网环境里更新和维护证书就会增加运营成本。
  • 实现逻辑?
    参照SSL的实现逻辑,与SSL的区别就是SSL的公钥是通过证书下发的,这里为了免去证书维护的麻烦,公钥直接下发给客户端。SSL处理流程如下:基于netty框架不使用SSL证书,实现websocket数据加密传输
    详情可参见本人另一篇博文: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方法即可。文章来源地址https://www.toymoban.com/news/detail-442043.html

  • 服务端调用示例:
@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();
        }

到了这里,关于基于netty框架不使用SSL证书,实现websocket数据加密传输的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用Netty实现SSL和TLS加密通信

            Netty支持使用SSL和TLS协议进行加密通信,可以在保证通信安全的同时,保证数据传输的完整性和可靠性。下面我们将演示如何使用Netty实现SSL和TLS加密通信。 首先,我们需要生成一个SSL证书,用于进行加密通信。可以使用OpenSSL等工具生成证书,也可以使用Netty提供

    2024年02月13日
    浏览(31)
  • Springboot中使用netty 实现 WebSocket 服务

    依赖 创建启动类 创建WebSocket 服务 WsServerInitialzer 初始化 创建信息ChatHandler 处理类

    2024年02月14日
    浏览(26)
  • 【项目实战】基于netty-websocket-spring-boot-starter实现WebSocket服务器长链接处理

    项目中需要建立客户端与服务端之间的长链接,首先就考虑用WebSocket,再来SpringBoot原来整合WebSocket方式并不高效,因此找到了netty-websocket-spring-boot-starter 这款脚手架,它能让我们在SpringBoot中使用Netty来开发WebSocket服务器,并像spring-websocket的注解开发一样简单 2.1.1 @ServerEndpo

    2024年02月12日
    浏览(38)
  • 创建 SSL证书并应用于WebSocket

    由于上一篇介绍 如何使用Fleck创建WebSocket服务器 ,感觉不够完善,因为生产环境中肯定是需要用到ssl的,而创建或申请ssl证书,相对而言是比较繁琐的事情,特别是本地如果要构建一个使用ssl的测试环境时,就难免要多费一番周折了。 本文介绍了如何创建一个 ssl 证书,用

    2024年02月02日
    浏览(28)
  • 配置域名SSL证书、阿里云OSS自定义域名的SSL证书,以及解决配置后WebSocket报错问题

    目录 1)为什么要配置SSL证书? 2)为什么阿里云OSS自定义域名也要配置SSL证书? 3)为什么WebSocket会报错呢?  解决方法 一、配置网站ssl证书 二、阿里云OSS自定义域名如何配置SSL证书  三、websocket失效后如何从ws换成wss 1)为什么要配置SSL证书? SSL证书是数字证书的一种,类

    2024年02月07日
    浏览(32)
  • 关于Nginx配置SSL证书(Https)和WebSocket的wss

    一. 生成SSL自签证书        自签证书就是自己生成的证书,免费的,不支持部署浏览器的,支持浏览器的就是收费的,需要购买,这里因为是本地测试,所以就用的自签证书,买的证书可以跳过证书生成部分.  安装OpenSSL           OpenSSL是生成SSL的工具,这里是在Win10下安装的,下载的

    2023年04月14日
    浏览(35)
  • 如何使用SpringBoot和Netty实现一个WebSocket服务器,并配合Vue前端实现聊天功能?

    本文将详细介绍如何使用SpringBoot和Netty实现一个WebSocket服务器,并配合Vue前端实现聊天功能。 WebSocket是一种基于TCP的协议,它允许客户端和服务器之间进行双向通信,而不需要像HTTP那样进行请求和响应。Netty是一个Java网络编程框架,它提供了强大的异步事件驱动网络编程能

    2024年02月16日
    浏览(31)
  • mac制作ssl证书|生成自签名证书,nodejs+express在mac上搭建https+wss(websocket)服务器

    mac 自带 openssl 所以没必要像 windows 一样先安装 openssl,直接生成即可 生成 key 让输入两次密码,随便,但是两次得是一样的 移除密码 生成 csr Country Name (2 letter code) [ 国家 ]:CN State or Province Name (full name) [ 省份 ]:Beijing Locality Name (eg, city) [ 城市 ]:Beijing Organization Name (eg, company)

    2024年02月09日
    浏览(31)
  • 【Netty】使用 SSL/TLS 加密 Netty 程序(二十)

    回顾Netty系列文章: Netty 概述(一) Netty 架构设计(二) Netty Channel 概述(三) Netty ChannelHandler(四) ChannelPipeline源码分析(五) 字节缓冲区 ByteBuf (六)(上) 字节缓冲区 ByteBuf(七)(下) Netty 如何实现零拷贝(八) Netty 程序引导类(九) Reactor 模型(十) 工作原理

    2024年02月07日
    浏览(25)
  • Netty 教程 – 实现WebSocket通讯

    WebSocket 协议是基于 TCP 的一种新的网络协议,它实现了浏览器与服务器 全双工(full-duplex)通信 ,允许 服务器主动发送信息给客户端 优点及作用 Http协议的弊端: Http协议为半双工协议。(半双工:同一时刻,数据只能在客户端和服务端一个方向上传输) Http协议冗长且繁琐 易

    2024年02月09日
    浏览(24)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包