zoukankan      html  css  js  c++  java
  • webSocket+jwt实现方式

    背景:

    原项目是通过前端定时器获取消息,存在消息滞后、空刷服务器、浪费带宽和资源的问题,在springboot项目集成websocket可以实现实时点对点消息推送。

    原项目是在header添加jwt令牌实现认证,由于websocket不支持在头部添加信息(或许是我打开的方式不对?),最终只能采用在url添加令牌参数实现认证,感觉不够优雅,后续再想办法重构改进。

    ps:至于放行websocket相关url,完全不要去考虑,危害巨大。

    1、websocket核心依赖

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

    2、config

    @Configuration
    public class WebSocketConfig {
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    }

    3、WebSocketServer

    @Slf4j
    @ServerEndpoint("/webSocket/{code}")
    @Component
    public class WebSocketServer {
        /**
         * concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。
         */
        private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
    
        /**
         * 与客户端的连接会话,需要通过它来给客户端发送数据
         */
        private Session session;
    
        /**
         * 接收识别码
         */
        private String code = "";
    
        /**
         * 连接建立成功调用的方法
         */
        @OnOpen
        public void onOpen(Session session, @PathParam("code") String code) {
            this.session = session;
            //如果存在就先删除一个,防止重复推送消息,实际这里实现了set,不删除问题也不大
            webSocketSet.removeIf(webSocket -> webSocket.code.equals(code));
            webSocketSet.add(this);
            this.code = code;
            log.info("建立WebSocket连接,code:" + code+",当前连接数:"+webSocketSet.size());
        }
    
        /**
         * 连接关闭调用的方法
         */
        @OnClose
        public void onClose() {
            webSocketSet.remove(this);
            log.info("关闭WebSocket连接,code:" + this.code+",当前连接数:"+webSocketSet.size());
        }
    
        /**
         * 收到客户端消息后调用的方法
         *
         * @param message 客户端发送过来的消息
         */
        @OnMessage
        public void onMessage(String message, Session session) {
            log.info("收到来[" + code + "]的信息:" + message);
    
        }
    
        @OnError
        public void onError(Session session, Throwable error) {
            log.error("websocket发生错误");
            error.printStackTrace();
        }
    
        /**
         * 实现服务器主动推送
         */
        private void sendMessage(String message) throws IOException {
            this.session.getBasicRemote().sendText(message);
        }
    
    
        /**
         * 群发自定义消息
         */
        public void sendAll(String message) {
            log.info("推送消息到" + code + ",推送内容:" + message);
            for (WebSocketServer item : webSocketSet) {
                try {
                    item.sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 定点推送
         */
        public void sendTo(String message, @PathParam("code") String code) {
            for (WebSocketServer item : webSocketSet) {
                try {
                    if (item.code.equals(code)) {
                        log.info("推送消息到[" + code + "],推送内容:" + message);
                        item.sendMessage(message);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            WebSocketServer that = (WebSocketServer) o;
            return Objects.equals(session, that.session) &&
                    Objects.equals(code, that.code);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(session, code);
        }
    }

    4、令牌过滤器

    @Slf4j
    @Component
    public class JwtTokenFilter extends OncePerRequestFilter {
    
        @Resource
        JwtProperties jwtProperties;
    
        @Resource
        TokenProvider tokenProvider;
    
        @Resource
        OnlineUserService onlineUserService;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            // http连接时,客户端应该是在头信息中携带令牌
            String authorizationHeader = request.getHeader(jwtProperties.getHeader());
            if(StringUtils.isBlank(authorizationHeader)) {
                // websocket连接时,令牌放在url参数上,以后重构
                authorizationHeader = request.getParameter(jwtProperties.getHeader());
            }
    
            String token = null;
            if(!StringUtils.isEmpty(authorizationHeader) && authorizationHeader.startsWith(jwtProperties.getTokenStartWith())){
                token = authorizationHeader.replace(jwtProperties.getTokenStartWith(),"");
            }
            //验证token
            if(StringUtils.isNotBlank(token) && tokenProvider.validateToken(token)){
                //验证token是否在缓存中
                OnlineUserDto onlineUserDto = onlineUserService.getOne(jwtProperties.getOnlineKey() + token);
                if(onlineUserDto!=null){
                    Authentication authentication = tokenProvider.getAuthentication(token, request);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                    log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), request.getRequestURI());
                }
            }
    
            filterChain.doFilter(request, response);
        }
    }

    5、在业务中调用方式(伪代码)

        @Resource
        private WebSocketServer webSocketServer;
    
        // 向客户端推送实时消息
        webSocketServer.sendTo(content, sysUser.getId());

    6、前端,伪代码

        getMessageCount() {
          getMyMessageCount().then(res => {
            const count = res
            this.messageCount = count > 0 ? count : null
          })
        },
        initWebSocket() {
          const wsUri = process.env.VUE_APP_WS_API + '/webSocket/' + this.user.id + '?Authorization=' + getToken()
          this.websock = new WebSocket(wsUri)
          this.websock.onopen = this.webSocketOnOpen
          this.websock.onerror = this.webSocketOnError
          this.websock.onmessage = this.webSocketOnMessage
        },
        webSocketOnOpen(e) {
          console.log('websocket 已经连接', e)
        },
        webSocketOnError(e) {
          this.$notify({
            title: 'WebSocket连接发生错误',
            type: 'error',
            duration: 0
          })
        },
        webSocketOnMessage(e) {
          const data = e.data
          this.$notify({
            title: '',
            message: data,
            type: 'success',
            dangerouslyUseHTMLString: true,
            duration: 5500
          })
          this.getMessageCount()
        }
  • 相关阅读:
    自动化测试===【转】Robot Framework作者建议如何选择自动化测试框架
    python实战===一行代码就能搞定的事情!
    python实战===石头剪刀布,简单模型
    python基础===取txt文件的若干行到另一个文件
    python基础===文件对象的访问模式,以及计数循环的使用方法
    linux===linux在线模拟器汇总
    python基础===两个list合并成一个dict的方法
    python基础===map和zip的用法
    python基础===正则表达式(转)
    python基础===python内置函数大全
  • 原文地址:https://www.cnblogs.com/asker009/p/13507877.html
Copyright © 2011-2022 走看看