欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

项目系统Netty的Channel和用户之间的关系绑定正确做法,以及Channel通道的安全性方案

程序员文章站 2022-05-19 08:06:50
前言 考虑一个功能业务,在 程序中向指定的某个用户进行实时通讯 在Web运用的 通讯功能中(如在线客服),为保证点对点通讯.而这个看似简单的根据用户寻到起 通道实际会碰到不少问题 1. web程序中的 协议是无状态的 2. 一般项目中 服务和 项目是独立部署的 3. 连接存在重连的情况,而 对象每次 ......

前言

考虑一个功能业务,在web程序中向指定的某个用户进行实时通讯

在web运用的socket通讯功能中(如在线客服),为保证点对点通讯.而这个看似简单的根据用户寻到起channel通道实际会碰到不少问题

  1. web程序中的http协议是无状态的
  2. 一般项目中socket服务和web项目是独立部署的
  3. socket连接存在重连的情况,而channel对象每次都不一样
  4. channel是面向网卡绑定的,无法序列化

解决方案

通过管理一个线程安全的用户标识(如用户主键)和对应channelmap链表

    private final concurrenthashmap<string, channel> channelmap = new concurrenthashmap<>();

那么问题来了,

  • netty模块中怎么得到这个用户标识?
  • 又如何保证netty socket模块可以安全的识别某个通道属于某个用户?(这个可以像上面一样的方式解决)
  • netty socket模块接收到一条消息又任何证明这条通道是可信的?

netty的实现中是没有认证也没有httpsession这个东西的,也就是说.在netty程序线程中是无法得到web项目登录的用户情况的.

出于这点,参考web项目集群的session共享方案.可以在redis等缓存中保存登录信息.

项目系统Netty的Channel和用户之间的关系绑定正确做法,以及Channel通道的安全性方案

  1. web项目中登录之后在redis中在这个以用户id为名的key中保存一个token,
  2. 在客户端socket通道建立之后立马发送包含一个用户标识asksocket服务端,
  3. 服务端根据ask计算一个tokenredis比对.一旦比对成功,则绑定当前channel和用户之间的关系;
  4. 之后server每接收到一条消息就检测当前通道有没有绑定用户信息

这个key一次性的.这点非常重要,试想一下.在你前台项目可能因为cookie过期或者后台已经自动将该用户下线,而你的用户标识ask暴露.那么就可能被恶意连接发送消息;

另外关于tokenask之类的验证传输如果仅仅是为了识别和绑定用户与channel的关系,这点也是可以忽略的,只要redis中保存该用户的登录状态即可,通道建立的第一次通讯就传输当前浏览器的登录用户标识,再去redis中比对即可,但是redis中的这个key还是一次性的好,避免一个用户建立多条socket通道

正确的绑定通道channel和用户之间的关系

如果我们仅仅有一个concurrenthashmap<string, channel>,是无法快速优雅的判断当前channel是属于哪个用户的;我看到别人绝大多数的实现是在创建一个channelid用户标识的map来管理

    //key为channel的长id,channel.id().aslongtext();value为用户id
    private final concurrenthashmap<string, string> channelandusermap = new concurrenthashmap<>();

其实这不是最合理的做法,正确的做法是利用channel对象提供的attributemap来保存该通道的附带信息,很多人不知道channel对象提供了一个绑定自定义数据的map
使用示例

    //用户id=>channel示例
    private final concurrenthashmap<string, channel> channelmap = new concurrenthashmap<>();


 /**
     * 判断一个通道是否有用户在使用
     * 可做信息转发时判断该通道是否合法
     * @param channel
     * @return
     */
    public boolean hasuser(channel channel) {
        attributekey<string> key = attributekey.valueof("user");
        return (channel.hasattr(key) || channel.attr(key).get() != null);//netty移除了这个map的remove方法,这里的判断谨慎一点
    }

    /**
     * 上线一个用户
     *
     * @param channel
     * @param userid
     */
    public void online(channel channel, string userid) {
        //先判断用户是否在web系统中登录?
        //这部分代码个人实现,参考上面redis中的验证
        
            this.channelmap.put(userid, channel);
            attributekey<string> key = attributekey.valueof("user");
            channel.attr(key).set(userid);
        

    }

    /**
     * 根据用户id获取该用户的通道
     *
     * @param userid
     * @return
     */
    public channel getchannelbyuserid(string userid) {
        return this.channelmap.get(userid);
    }

    /**
     * 判断一个用户是否在线
     *
     * @param userid
     * @return
     */
    public boolean online(string userid) {
        return this.channelmap.containskey(userid) && this.channelmap.get(userid) != null;
    }

注意!!

很多人拿channel.id().asshorttext()来记录标识channel,这是错误的!!!!!短id不保证全局唯一!!

项目系统Netty的Channel和用户之间的关系绑定正确做法,以及Channel通道的安全性方案