当前位置: 首页 > news >正文

基于Netty框架实现的WebSocket服务器握手认证传参笔记

Netty的WebSocketServerProtocolHandler负责处理WebSocket的握手,但具体的参数处理可能需要自定义Handler来处理HTTP请求。

认证参数传递方式

WebSocket握手基于HTTP协议,可通过以下方式传递认证参数:
URL Query参数ws://host:port/path?token=xxx&user=123
HTTP HeadersAuthorizationX-Auth-Token等自定义头
Cookies:在Cookie中存储认证信息
Protocal : 重写协议头传参


Netty实现流程

1 配置Pipeline
public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        
        // HTTP编解码器
        pipeline.addLast(new HttpServerCodec());
        // 聚合HTTP完整请求
        pipeline.addLast(new HttpObjectAggregator(65536));
        // URL Query参数
        pipeline.addLast(new MyHttpHeaderHandler());
        // 自定义认证处理器
   		// pipeline.addLast(new AuthHandler());
        // WebSocket协议处理器
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        
        // 自定义handler ,处理业务逻辑
        // pipeline.addLast(new MyHandShakeHandler());
        // WebSocket业务处理器
        pipeline.addLast(new WebSocketFrameHandler());
    }
}
2 自定义认证处理器
URL Query参数
public class MyHttpHeaderHandler extends ChannelInboundHandlerAdapter {
    /**
     * 处理接收到的通道数据事件
     * @param ctx 通道处理上下文,用于访问通道和事件流
     * @param msg 接收到的消息对象,类型为Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if(msg instanceof FullHttpRequest){
            // 解析HTTP请求并处理URI参数
            FullHttpRequest req = (FullHttpRequest) msg;
            UrlBuilder urlBuilder =  UrlBuilder.ofHttp(req.uri());

            // 提取查询参数中的token值并设置到通道属性
            Optional<String> tokenOptional = Optional.of(urlBuilder)
                    .map(UrlBuilder::getQuery)
                    .map(urlQuery -> urlQuery.get("token"))
                    .map(CharSequence::toString);
            tokenOptional.ifPresent(s -> NettyUtil.setAttr(ctx.channel(), NettyUtil.TOKEN, s));

            // 重置请求URI为路径部分(去除查询参数)
            req.setUri(urlBuilder.getPath().toString());
        }
        // 继续传递事件到处理流水线的下一个环节
        ctx.fireChannelRead(msg);
    }
	
}

获取前端token

Channel channel = ctx.channel();

if(evt instanceof WebSocketServerProtocolHandler.HandshakeComplete){
     webSocketService.connect(channel);
     String token = NettyUtil.getAttr(channel, NettyUtil.TOKEN);
     if(StrUtil.isNotBlank(token))
     {
         webSocketService.authorize(channel,new WSAuthorizeReq(token));
     }
     log.info("握手完成");
 }
HTTP Headers
public class AuthHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        //  解析认证参数
        String token = getToken(request);
        String clientId = getClientId(request);
        
        //  执行认证逻辑
        if (!validateToken(token)) {
            sendHttpResponse(ctx, request, new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED));
            ctx.close();
            return;
        }
        
        //  认证通过,存储用户信息
        AttributeKey<String> clientKey = AttributeKey.valueOf("clientId");
        ctx.channel().attr(clientKey).set(clientId);
        
        //  传递修改后的请求给后续处理器
        request.setUri("/ws"); // 清理query参数
        ctx.fireChannelRead(request.retain());
    }

    private String getToken(FullHttpRequest request) {
        // 从Query参数获取
        QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
        List<String> tokens = decoder.parameters().get("token");
        if (tokens != null && !tokens.isEmpty()) {
            return tokens.get(0);
        }
        // 从头信息获取
        return request.headers().get("X-Auth-Token");
    }

    private boolean validateToken(String token) {
        // 实现具体的token验证逻辑
        return "valid_token".equals(token);
    }

    private void sendHttpResponse(...) {
        // 发送HTTP响应实现
    }
}
Protocal传参

@AllArgsConstructor
public class MyHandShakeHandler extends ChannelInboundHandlerAdapter {

    /**
     * WebSocket握手处理器,负责处理客户端的HTTP请求并完成WebSocket握手。
     * 继承自ChannelInboundHandlerAdapter以处理入站事件。
     */

    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
        /**
         * 处理接收到的通道读取事件,主要处理WebSocket握手逻辑。
         *
         * @param ctx 通道处理上下文,用于访问通道和事件派发
         * @param msg 接收到的消息对象,预期为HTTP请求或相关对象
         */
        HttpObject httpObject = (HttpObject) msg;

        // 处理WebSocket握手逻辑
        if (httpObject instanceof HttpRequest) {
            final HttpRequest req = (HttpRequest) httpObject;

            // 从请求头中提取WebSocket协议标识token
            String token = req.headers().get("Sec-Websocket-Protocol");

            // 将token保存到通道属性中以便后续使用
            Attribute<Object> token1 = ctx.channel().attr(AttributeKey.valueOf("token"));
            token1.set(token);

            // 根据请求URI和协议创建WebSocket握手工厂
            WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(req.getUri(), token, false);
            final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);

            // 如果握手协商器为空(版本不匹配),发送不支持响应
            if (handshaker == null) {
                WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
            } else {
                // 移除当前处理器,避免重复处理
                ctx.pipeline().remove(this);

                // 执行握手并监听结果
                ChannelFuture handshakeFuture = handshaker.handshake(ctx.channel(), req);
                handshakeFuture.addListener(new ChannelFutureListener() {
                    public void operationComplete(ChannelFuture future) {
                        // 监听握手完成事件,根据结果触发相应事件
                        if (!future.isSuccess()) {
                            ctx.fireExceptionCaught(future.cause());
                        } else {
                            ctx.fireUserEventTriggered(WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE);
                        }
                    }
                });
            }
        } else {
            // 转发非HTTP请求的消息到下一个处理器
            ctx.fireChannelRead(msg);
        }
    }
}

调用

	if(evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE){
		log.info("握手完成");
		Attribute<Object> token =  channel.attr(AttributeKey.valueOf("token"));
		webSocketService.authorize(channel,new WSAuthorizeReq(token.get().toString()));
	 }

关键点说明

Pipeline顺序
认证处理器必须位于WebSocketServerProtocolHandler之前
使用HttpObjectAggregator确保接收完整HTTP请求

参数清理

request.setUri("/ws"); // 移除query参数

避免敏感参数被后续处理器看到

用户信息传递

// 存储
ctx.channel().attr(AttributeKey.valueOf("user")).set(userInfo);

// 在后续处理器获取
String user = ctx.channel().attr(AttributeKey.valueOf("user")).get();

错误处理
返回标准HTTP状态码:
401 Unauthorized:认证失败
403 Forbidden:权限不足
及时关闭无效连接


客户端示例(JavaScript)

const ws = new WebSocket("ws://localhost:8080/ws?token=my_token");
// 或使用头信息
const ws = new WebSocket("ws://localhost:8080/ws", {
  headers: {
    "X-Auth-Token": "my_token"
  }
});

或者直接控制台new一个

在这里插入图片描述

可以得到后端获取到前端传的token参数以及重新使用token获取用户信息成果

在这里插入图片描述

增强安全建议

使用加密连接:强制使用wss://
时效性验证:增加时间戳参数防止重放攻击
签名机制:对参数进行HMAC签名
限流控制:防止暴力破解


使用curl模拟握手请求:

curl -i -N -H "Connection: Upgrade" \
     -H "Upgrade: websocket" \
     -H "Host: localhost:8080" \
     -H "X-Auth-Token: invalid_token" \
     http://localhost:8080/ws

相关文章:

  • Python包中的“守门员“:深入理解__init__.py的魔法
  • systemd-networkd 的 *.network 配置文件中的 [Network] 和 [Address] 中的 Address 有个什么区别?
  • 云服务器怎么防御ddos攻击呢?
  • M系mac怎么关闭sip
  • 三相永磁同步电机的控制方法之矢量控制
  • MySQL-----视图与索引
  • 搜索引擎工作原理图解:抓取→索引→排名全链路拆解
  • 7.2 控件和组件
  • Flink 自定义数据源:从理论到实践的全方位指南
  • langchain+ollama+deepseek的部署(win)
  • 北京交通大学第三届C语言积分赛
  • Hugging Face 量化部署指南
  • 详解Redis的持久化与数据可靠性
  • 清晰易懂的 Maven 彻底卸载与清理教程
  • S32K144外设实验(五):FTM周期中断
  • 右击没有Word、PPT、Excel功能
  • 大模型架构记录【RAG优化】
  • 机器视觉工程师如何看机器视觉展会,有些机器视觉兄弟参加机器视觉展会,真的是参加了?重在参与?
  • Java高频面试之集合-17
  • 常见的表单元素
  • 媒体谈法院就“行人相撞案”道歉:执法公正,普法莫拉开“距离”
  • 佩斯科夫:俄会考虑30天停火提议,但试图对俄施压无用
  • 上报集团社长李芸:发挥媒体优势,让中非民心在数字时代更深层互联互通
  • 14岁女生瞒报年龄文身后洗不掉,法院判店铺承担六成责任
  • 重温经典|《南郭先生》:不模仿别人,不重复自己
  • 经彩申城!上海网络大V沙龙活动走进闵行