From ec8b4a83925104ed496cf458bc00cee1316f9c66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BF=9C=E6=96=B9=E5=A4=95=E9=98=B3?= Date: Tue, 15 Feb 2022 19:52:32 +0800 Subject: [PATCH] =?UTF-8?q?websocket=E6=94=AF=E6=8C=81=E5=9C=A8=E6=8F=A1?= =?UTF-8?q?=E6=89=8B=E6=97=B6=E9=89=B4=E6=9D=83=20=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=9C=B0=E5=9D=80:https://www.yuque.com/yuanfangxiyang/ma4ytb/?= =?UTF-8?q?vvy3iz#mmdUX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 4.0.0.VERSION => 4.1.0.VERSION | 0 README.md | 7 ++ ...0.0.jar => cim-server-sdk-netty-4.1.0.jar} | Bin 79800 -> 82957 bytes cim-boot-server/pom.xml | 11 ++- .../predicate/HandshakePredicate.java | 31 ++++++++ .../com/farsunset/cim/config/CIMConfig.java | 4 +- cim-server-sdk/pom.xml | 6 +- .../server/handler/CIMNioSocketAcceptor.java | 16 +++- .../sdk/server/handshake/HandshakeEvent.java | 75 ++++++++++++++++++ .../server/handshake/HandshakeHandler.java | 73 +++++++++++++++++ .../cim/sdk/server/model/ReplyBody.java | 1 - 11 files changed, 211 insertions(+), 13 deletions(-) rename 4.0.0.VERSION => 4.1.0.VERSION (100%) rename cim-boot-server/libs/{cim-server-sdk-netty-4.0.0.jar => cim-server-sdk-netty-4.1.0.jar} (74%) create mode 100644 cim-boot-server/src/main/java/com/farsunset/cim/component/predicate/HandshakePredicate.java create mode 100644 cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handshake/HandshakeEvent.java create mode 100644 cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handshake/HandshakeHandler.java diff --git a/4.0.0.VERSION b/4.1.0.VERSION similarity index 100% rename from 4.0.0.VERSION rename to 4.1.0.VERSION diff --git a/README.md b/README.md index fc928e1..54661ce 100644 --- a/README.md +++ b/README.md @@ -136,4 +136,11 @@ CIM采用业内主流开源技术构建,易于扩展和使用,并完美支 5.文档放到语雀在线文档 6.其他30多处多处代码优化 + +------------------------------------------------------------------------------------------- +版本:4.1.0/时间:2022-02-15 + +1.websocket支持在握手时鉴权验证 +2.websocketPath 由 "/" 变更为 "" wss和ws链接地址后面不需要加/ + diff --git a/cim-boot-server/libs/cim-server-sdk-netty-4.0.0.jar b/cim-boot-server/libs/cim-server-sdk-netty-4.1.0.jar similarity index 74% rename from cim-boot-server/libs/cim-server-sdk-netty-4.0.0.jar rename to cim-boot-server/libs/cim-server-sdk-netty-4.1.0.jar index 8591804d0173c2d6a9c314d2e329ab5aee8e1a8a..887f076e862d04c27bf8c200c2c3f90d8fef0d89 100644 GIT binary patch delta 15909 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 delta 13185 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.farsunset cim-boot-server - 1.0.0 + 1.1.0 org.springframework.boot @@ -17,8 +17,8 @@ 1.8 - 4.1.65.Final - 3.17.0 + 4.1.70.Final + 3.19.2 8.0.22 2.8.0 3.0.0 @@ -62,10 +62,9 @@ com.farsunset cim-server-sdk - 4.0.0 + 4.1.0 system - - ${project.basedir}/libs/cim-server-sdk-netty-4.0.0.jar + ${project.basedir}/libs/cim-server-sdk-netty-4.1.0.jar org.apache.commons diff --git a/cim-boot-server/src/main/java/com/farsunset/cim/component/predicate/HandshakePredicate.java b/cim-boot-server/src/main/java/com/farsunset/cim/component/predicate/HandshakePredicate.java new file mode 100644 index 0000000..aa924a9 --- /dev/null +++ b/cim-boot-server/src/main/java/com/farsunset/cim/component/predicate/HandshakePredicate.java @@ -0,0 +1,31 @@ +package com.farsunset.cim.component.predicate; + +import com.farsunset.cim.sdk.server.handshake.HandshakeEvent; +import org.springframework.stereotype.Component; + +import java.util.function.Predicate; + +/** + * WS链接握手鉴权验证 + */ +@Component +public class HandshakePredicate implements Predicate { + + /** + * + * @param event + * @return true验证通过 false验证失败 + */ + @Override + public boolean test(HandshakeEvent event) { + + /* + 可通过header或者uri传递参数 + String token = event.getHeader("token"); + String token = event.getParameter("token"); + do auth.... + */ + + return true; + } +} diff --git a/cim-boot-server/src/main/java/com/farsunset/cim/config/CIMConfig.java b/cim-boot-server/src/main/java/com/farsunset/cim/config/CIMConfig.java index 9a12c01..45ea155 100644 --- a/cim-boot-server/src/main/java/com/farsunset/cim/config/CIMConfig.java +++ b/cim-boot-server/src/main/java/com/farsunset/cim/config/CIMConfig.java @@ -1,6 +1,7 @@ package com.farsunset.cim.config; import com.farsunset.cim.component.handler.annotation.CIMHandler; +import com.farsunset.cim.component.predicate.HandshakePredicate; import com.farsunset.cim.config.properties.CIMProperties; import com.farsunset.cim.sdk.server.group.SessionGroup; import com.farsunset.cim.sdk.server.group.TagSessionGroup; @@ -43,11 +44,12 @@ public class CIMConfig implements CIMRequestHandler, ApplicationListenercom.farsunset cim-server-sdk-netty - 4.0.0 + 4.1.0 jar UTF-8 UTF-8 1.8 - 3.11.1 - 4.1.60.Final + 3.19.3 + 4.1.70.Final 1.7.30 diff --git a/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handler/CIMNioSocketAcceptor.java b/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handler/CIMNioSocketAcceptor.java index 8e2004f..d6d585e 100644 --- a/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handler/CIMNioSocketAcceptor.java +++ b/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handler/CIMNioSocketAcceptor.java @@ -27,6 +27,8 @@ import com.farsunset.cim.sdk.server.coder.WebMessageDecoder; import com.farsunset.cim.sdk.server.coder.WebMessageEncoder; import com.farsunset.cim.sdk.server.constant.CIMConstant; import com.farsunset.cim.sdk.server.constant.ChannelAttr; +import com.farsunset.cim.sdk.server.handshake.HandshakeEvent; +import com.farsunset.cim.sdk.server.handshake.HandshakeHandler; import com.farsunset.cim.sdk.server.model.Ping; import com.farsunset.cim.sdk.server.model.SentBody; import io.netty.bootstrap.ServerBootstrap; @@ -50,6 +52,7 @@ import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; @Sharable public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler{ @@ -68,6 +71,7 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler{ private final Integer appPort; private final Integer webPort; private final CIMRequestHandler outerRequestHandler; + private final HandshakeHandler handshakeHandler; private final ChannelHandler loggingHandler = new LoggingHandler(); @@ -85,6 +89,7 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler{ this.webPort = builder.webPort; this.appPort = builder.appPort; this.outerRequestHandler = builder.outerRequestHandler; + this.handshakeHandler = new HandshakeHandler(builder.handshakePredicate); bossThreadFactory = r -> { Thread thread = new Thread(r); @@ -183,8 +188,9 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler{ public void initChannel(SocketChannel ch){ ch.pipeline().addLast(new HttpServerCodec()); ch.pipeline().addLast(new ChunkedWriteHandler()); - ch.pipeline().addLast(new HttpObjectAggregator(65536)); - ch.pipeline().addLast(new WebSocketServerProtocolHandler("/",false)); + ch.pipeline().addLast(new HttpObjectAggregator(4 * 1024)); + ch.pipeline().addLast(new WebSocketServerProtocolHandler("",false)); + ch.pipeline().addLast(handshakeHandler); ch.pipeline().addLast(new WebMessageDecoder()); ch.pipeline().addLast(new WebMessageEncoder()); ch.pipeline().addLast(loggingHandler); @@ -296,6 +302,7 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler{ private Integer appPort; private Integer webPort; private CIMRequestHandler outerRequestHandler; + private Predicate handshakePredicate; public Builder setAppPort(Integer appPort) { this.appPort = appPort; @@ -315,6 +322,11 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler{ return this; } + public Builder setHandshakePredicate(Predicate handshakePredicate) { + this.handshakePredicate = handshakePredicate; + return this; + } + public CIMNioSocketAcceptor build(){ return new CIMNioSocketAcceptor(this); } diff --git a/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handshake/HandshakeEvent.java b/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handshake/HandshakeEvent.java new file mode 100644 index 0000000..1c3dae9 --- /dev/null +++ b/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handshake/HandshakeEvent.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013-2019 Xia Jun(3979434@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *************************************************************************************** + * * + * Website : http://www.farsunset.com * + * * + *************************************************************************************** + */ +package com.farsunset.cim.sdk.server.handshake; + +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.QueryStringDecoder; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; + +import java.util.List; + +/** + * websocket客户端握手请求信息 + * 用于在握手阶段鉴权 + */ +public class HandshakeEvent { + + private final String uri; + + private final HttpHeaders header; + + public HandshakeEvent(String uri, HttpHeaders header) { + this.uri = uri; + this.header = header; + } + + public String getHeader(String name){ + return header.get(name); + } + + public List getHeaders(String name){ + return header.getAll(name); + } + + public Integer getIntHeader(String name){ + return header.getInt(name); + } + + public String getParameter(String name){ + QueryStringDecoder decoder = new QueryStringDecoder(uri); + List valueList = decoder.parameters().get(name); + return valueList == null || valueList.isEmpty() ? null : valueList.get(0); + } + + public List getParameters(String name){ + QueryStringDecoder decoder = new QueryStringDecoder(uri); + return decoder.parameters().get(name); + } + + public String getUri() { + return uri; + } + + public static HandshakeEvent of(WebSocketServerProtocolHandler.HandshakeComplete event){ + return new HandshakeEvent(event.requestUri(),event.requestHeaders()); + } +} diff --git a/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handshake/HandshakeHandler.java b/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handshake/HandshakeHandler.java new file mode 100644 index 0000000..fc00bd4 --- /dev/null +++ b/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/handshake/HandshakeHandler.java @@ -0,0 +1,73 @@ +/* + * Copyright 2013-2019 Xia Jun(3979434@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *************************************************************************************** + * * + * Website : http://www.farsunset.com * + * * + *************************************************************************************** + */ +package com.farsunset.cim.sdk.server.handshake; + +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketCloseStatus; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; + +import java.util.function.Predicate; + +/** + * WS握手时鉴权 + */ +@ChannelHandler.Sharable +public class HandshakeHandler extends ChannelInboundHandlerAdapter { + + private final Predicate handshakePredicate; + + private final WebSocketCloseStatus closeStatus = new WebSocketCloseStatus(HttpResponseStatus.UNAUTHORIZED.code(),HttpResponseStatus.UNAUTHORIZED.reasonPhrase()); + + public HandshakeHandler(Predicate handshakePredicate) { + this.handshakePredicate = handshakePredicate; + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + + super.userEventTriggered(ctx, evt); + + if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) { + doAuthentication(ctx, (WebSocketServerProtocolHandler.HandshakeComplete) evt); + } + + } + + private void doAuthentication(ChannelHandlerContext context, WebSocketServerProtocolHandler.HandshakeComplete event) { + + if (handshakePredicate == null) { + return; + } + + /* + * 鉴权不通过,关闭链接 + */ + if (!handshakePredicate.test(HandshakeEvent.of(event))) { + context.channel().writeAndFlush(new CloseWebSocketFrame(closeStatus)).addListener(ChannelFutureListener.CLOSE); + } + } +} \ No newline at end of file diff --git a/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/model/ReplyBody.java b/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/model/ReplyBody.java index 8ecba71..578e947 100644 --- a/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/model/ReplyBody.java +++ b/cim-server-sdk/src/main/java/com/farsunset/cim/sdk/server/model/ReplyBody.java @@ -28,7 +28,6 @@ import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.function.BiConsumer; /** * 请求应答对象