以前写netty的聊天工具时遇到个问题,就是websocket连接时验证当前用户是否已经拥有登陆权限,
当时想的是像http请求一样,通过url直接传参然后后台解析不通过关闭通道,但是发现url上一增加参数
就无法正常连接,当时时间紧未进行处理,当时处理逻辑如下:
1、先设置读写超时120秒
2、连接成功后增加当前通道验证标识
2、前端监听websocket的onopen方法,一但连接立马发送验证消息,后台验证通过后修改验证标识
3、前端每过50秒向后台发送一个心跳包,后端收到心跳包判断是否已通过验证,通过后回复心跳包,未通过不回复,不回复的话120秒后该通道会被自动关闭
贴下相关代码:
//连接后
socket.onopen = function(event) {
IMSocket.bindUserHandle('${userId}','${sessionId}','online');//绑定通道为当前用户
openHeartBeat();//开启心跳
flushNoReadFrendMes();//展示未读消息
if(layui.layim.cache().mine.status=="online"){
IMSocket.statusnotice('${userId}',3);//通知我上线了
}
if(showclosewebsocketlayer){
showclosewebsocketlayer=false;
layer.close(showclosewebsocketindex);
}
};
/**
* 消息处理
*
* @param hander
* @param wrapper
*/
private void receiveMessages(ChannelHandlerContext hander, IMBaseMessage baseMessage) {
if(baseMessage.getCmdType()==Constants.CmdType.BIND){//绑定登陆人
imConnertor.bindChannel(hander, baseMessage);
}else if(baseMessage.getCmdType()==Constants.CmdType.MESSAGE){//发送消息
iLayImMessageService.flushSessionTime(hander);
baseMessage = iLayImMessageService.saveMessageToDB(baseMessage);
imConnertor.sendFrendMessage(baseMessage);
}else if(baseMessage.getCmdType()==Constants.CmdType.READMES){//消息已读
iLayImMessageService.flushSessionTime(hander);
iLayImMessageService.readMes(baseMessage);
}else if(baseMessage.getCmdType()==Constants.CmdType.ONLINE||baseMessage.getCmdType()==Constants.CmdType.OFFLINE){//上线 下线
List<MyMember> myMembers = iLayImMessageService.userNoticeSatus(baseMessage);
imConnertor.userNoticeSatus(baseMessage, myMembers);
}else if(baseMessage.getCmdType()==Constants.CmdType.HEARTBEAT){//心跳
imConnertor.heartbeat(hander);
}
}
现在补充另外一种方式基于url参数的验证:
跟踪源码发现在类WebSocketServerProtocolHandshakeHandler的channelRead方法有判断请求的地址是否与我们开启监听服务的配置的地址是否一样,如果不一样就停止该通道。如图:
class WebSocketServerProtocolHandshakeHandler extends ChannelInboundHandlerAdapter {
private final String websocketPath;
private final String subprotocols;
private final boolean allowExtensions;
private final int maxFramePayloadSize;
private final boolean allowMaskMismatch;
WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols,
boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch) {
this.websocketPath = websocketPath;
this.subprotocols = subprotocols;
this.allowExtensions = allowExtensions;
maxFramePayloadSize = maxFrameSize;
this.allowMaskMismatch = allowMaskMismatch;
}
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
final FullHttpRequest req = (FullHttpRequest) msg;
if (!websocketPath.equals(req.uri())) {
ctx.fireChannelRead(msg);
return;
}
try {
if (req.method() != GET) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
return;
}
发现问题就好处理了,处理如下,将WebSocketServerProtocolHandshakeHandler放在TextWebSocketFrameHandler之后,然后TextWebSocketFrameHandler重写channelRead方法,将url上的参数取出来使用后,将地址设置回去就行了。如果是验证登陆权限的话,验证不能过调用ChannelHandlerContext.close()直接关闭通道,避免资源浪费。
.childHandler(new ChannelInitializer<Channel>(){//同上
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(READ_IDEL_TIME_OUT,
WRITE_IDEL_TIME_OUT, ALL_IDEL_TIME_OUT, TimeUnit.SECONDS));
pipeline.addLast(new HeartbeatServerHandler()); // 2 集成心跳处理
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new TextWebSocketFrameHandler(iLayImMessageService,imConnertor));
pipeline.addLast(new WebSocketServerProtocolHandler(WebsocketChatServer.WEB_SOCKET_LINKURL, null, true, 10485760));
pipeline.addLast(new HttpRequestHandler(WebsocketChatServer.HTTP_SOCKET_LINKURL));
}})
public class TextWebSocketFrameHandler extends
SimpleChannelInboundHandler<TextWebSocketFrame> {
.....
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (ObjectHelper.isNotEmpty(msg) && msg instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) msg;
String uri = request.uri();
String origin = request.headers().get("Origin");
if (null == origin) {
ctx.close();
} else {
if (null != uri && uri.contains("?")) {
String[] uriArray = uri.split("\\?");
if (null != uriArray && uriArray.length > 1) {
String[] paramsArray = uriArray[1].split("=");
if (null != paramsArray && paramsArray.length > 1) {
//TODO 验证连接权限,不通过关闭
}
}
request.setUri(WebsocketChatServer.WEB_SOCKET_LINKURL);
}
}
}
super.channelRead(ctx, msg);
}
.....