SpringBoot整合webSocket并增加用户权限验证

SpringBoot整合webSocket并增加用户权限验证

1、网站中的消息功能如何实现

思考:像这样的消息功能怎么实现?
如果网页不刷新,服务端有新消息如何推送到浏览器?
解决方案,采用轮询的方式。即:通过js不断的请求服务器,查看是否有新数据,如果有,就获取到新数据。
这种解决方法是否存在问题呢?
当然是有的,如果服务端一直没有新的数据,那么js也是需要一直的轮询查询数据,这就是一种资源的浪费。
那么,有没有更好的解决方案? 有!那就是采用WebSocket技术来解决。

2、什么是WebSocket?

WebSocket 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助
HTTP请求完成。 WebSocket是真正实现了全双工通信的服务器向客户端推的互联网技术。 它是一种在单个TCP连
接上进行全双工通讯协议。Websocket通信协议与2011年倍IETF定为标准RFC 6455,Websocket API被W3C定为
标准。

全双工和单工的区别?

  • 全双工(Full Duplex)是通讯传输的一个术语。通信允许数据在两个方向上同时传输,它在能力上相当
    于两个单工通信方式的结合。全双工指可以同时(瞬时)进行信号的双向传输(A→B且B→A)。指
    A→B的同时B→A,是瞬时同步的。
  • 单工、半双工(Half Duplex),所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,
    一条窄窄的马路,同时只能有一辆车通过,当目前有两辆车对开,这种情况下就只能一辆先过,等到头
    儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是
    基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台。

3、http与websocket的区别

3.1、http

http协议是短连接,因为请求之后,都会关闭连接,下次重新请求数据,需要再次打开链接。
image

3.2、webSocket

WebSocket协议是一种长链接,只需要通过一次请求来初始化链接,然后所有的请求和响应都是通过这个TCP链接
进行通讯。
image

4、快速入门

4.1、创建SpringBoot-websocket工程

在基础工程中新建module,命名为spring-websocket或其他名字都可以,这里可以根据自己的需求去创建相应的名称,创建过程如下:

  • 创建模块
    image
    点击next,输入模块名称点击Finish就可以啦
    image

4.2、配置spring-websocket工程

  • 配置pom文件

这里使用的是springboot框架,我们直接使用对应的starter就可以啦,我在pom文件中引入相应的配置如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- websocket依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>

</dependencies>
  • 创建项目启动类

src.main.java下创建自己定义的包名,这里定义的包名称是cn.org.spring.tools.websocket,在包下面创建springboot的启动类WebSocketApplication

@SpringBootApplication
public class WebSocketApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebSocketApplication.class);
    }
}
  • 创建webSocket配置类

cn.org.spring.tools.websocket新建一个package下新建config包,在config包中新建WebSocketConfig类,配置如下

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    /**
     * 注入拦截器
     */
    @Resource
    private MyHandshakeInterceptor myHandshakeInterceptor;


    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        
        webSocketHandlerRegistry
                //添加myHandler消息处理对象,和websocket访问地址
                .addHandler(myHandler(), "/ws")
                //设置允许跨域访问
                .setAllowedOrigins("*")
                //添加拦截器可实现用户链接前进行权限校验等操作
                .addInterceptors(myHandshakeInterceptor);
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyWebSocketHandler();
    }
}
  • 实现MyWebSocketHandler

新建包handler,创建MyWebSocketHandler并集成TextWebSocketHandler,TextWebSocketHandler是主要处理string类型消息,这里我们可以继承其他的handler类,如BinaryWebSocketHandler

public class MyWebSocketHandler extends TextWebSocketHandler {

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static AtomicInteger onlineNum = new AtomicInteger();
    //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
    private static ConcurrentHashMap<String, WebSocketSession> sessionPools = new ConcurrentHashMap<>();

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message)
            throws IOException {
        System.out.println("获取到消息 >> " + message.getPayload());
        session.sendMessage(new TextMessage(String.format("收到用户:【%s】发来的【%s】",
                session.getAttributes().get("uid"), message.getPayload())));
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws
            Exception {
        System.out.println("获取到拦截器中用户ID : " + session.getAttributes().get("uid"));
        String uid = session.getAttributes().get("uid").toString();
        //TODO: 重复链接没有进行处理
        sessionPools.put(uid, session);
        addOnlineCount();
        System.out.println(uid + "加入webSocket!当前人数为" + onlineNum);
        session.sendMessage(new TextMessage("欢迎连接到ws服务! 当前人数为:" + onlineNum));
    }


    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status)
            throws Exception {
        System.out.println("断开连接!");
        String uid = session.getAttributes().get("uid").toString();
        sessionPools.remove(uid);
        subOnlineCount();
    }

    /**
     * 添加链接人数
     */
    public static void addOnlineCount() {
        onlineNum.incrementAndGet();
    }

    /**
     * 移除链接人数
     */
    public static void subOnlineCount() {
        onlineNum.decrementAndGet();
    }

}
  • 创建拦截器Interceptor

创建链接前权限验证拦截器Interceptor,这里我们继承HandshakeInterceptor具体实现如下:

@Component
public class MyHandshakeInterceptor implements HandshakeInterceptor {
    /**
     * 握手之前,若返回false,则不建立链接 *
     *
     * @param request
     * @param response
     * @param wsHandler
     * @param attributes
     * @return
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse
            response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
        //将用户id放入socket处理器的会话(WebSocketSession)中
        ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
        //获取参数
        String userId = serverHttpRequest.getServletRequest().getParameter("userId");
        attributes.put("uid", userId);
        //可以在此处进行权限验证,当用户权限验证通过后,进行握手成功操作,验证失败返回false
        if (userId.equals("123")) {
            System.out.println("握手失败.....");
            return false;
        }
        System.out.println("开始握手。。。。。。。");
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse
            response, WebSocketHandler wsHandler, Exception exception) {
        System.out.println("握手成功啦。。。。。。");
    }
}
  • 启动测试

Springboot 默认启动端口为8080,可以采用默认端口,也可以自定义端口,这么我们新增application.yml配置文件,定义服务端口和应用名称;

server:
  port: 9001
spring:
  application:
    name: spring-websocket

配置好后,启动服务;

这里给大家分享个ws在线测试工具,工具地址

我们尝试链接下我们的ws服务:
image
在地址栏输入地址ws//127.0.0.1:9001/ws?userId=123
点击链接
如出现一下信息说明链接成功。
image

  • 连接成功后,进行发送消息测试

看到如下信息说明服务端收到前端消息并返回了对应的消息
image

至此,我们集成websocket完成,代码中还有些细节需要去优化,大家在借鉴使用时不要直接copy,要结合自己的业务场景去实现细化它。

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页