webSocket拦截器权限校验

4,043 阅读4分钟

最坑的是后端拦截器、Handler注入问题,以及前端使用的时机控制

1.后端

1.1配置类

package com.glodon.webSocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * @author lihongxiang
 * @ 2020/5/25 20:41
 */
@Configuration
@EnableWebSocket
public class WsConfigure implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry)
    {
        registry.addHandler(myHandler(), "/websocket").setAllowedOrigins("*").addInterceptors(new WebSocketInterceptor());
    }

    /**
     * 向spring容器注册javabean由spring容器来管理
     * @return
     */
    @Bean
    public WsHandler myHandler()
    {
        return new WsHandler();
    }

}

1.2拦截器

package com.glodon.webSocket;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.glodon.common.service.EBQEditAuthAPIService;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

/**
 * @author lihongxiang
 * @ 2020/5/26 8:33
 */
@Component
public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {


    private EBQEditAuthAPIService verificationService = SpringUtil.getBean(EBQEditAuthAPIService.class);

    /**
     * 当客户端与服务器端握手之前之前执行的方法
     * 取出当前存在session的用户信息,封装到WebSocket对象中的map中;
     * 由Handler处理器中获取id
     * @return
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) throws Exception {
        //将增强的request转换httpservletRequest
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
            HttpServletRequest servletRequest = serverHttpRequest.getServletRequest();
            String account = servletRequest.getParameter("account");
            String token = servletRequest.getParameter("token");
            attributes.put("account",account);
            attributes.put("token",token);
            boolean isValid = verificationService.checkUserByToken(account, token);
            if (isValid) {
                System.out.println("dddddd");
            }
            return super.beforeHandshake(request, response, wsHandler, attributes);
        }
        //放行
        return true;
    }

    /**
     * 与服务器websoket建立握手之后执行的方法
     */
    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Exception exception) {

    }

}

1.3Handler

package com.glodon.webSocket;

import com.glodon.common.service.EBQEditAuthAPIService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author lihongxiang
 * @ 2020/5/25 20:40
 */
@Component
public class WsHandler extends TextWebSocketHandler {

    private Logger logger = LoggerFactory.getLogger(WsHandler.class);

    @Autowired
    private EBQEditAuthAPIService verificationService;

    @Autowired
    private RedisTemplate redisTemplate;

    // concurrent包的线程安全Map,用来存放每个客户端对应的WebSocketSession对象。
    public static ConcurrentHashMap<String,WebSocketSession> webSocketMap = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception
    {
        String account = (String) session.getAttributes().get("account");
        String token = (String) session.getAttributes().get("token");

        if (!redisTemplate.hasKey(token))
        {
            logger.error("redis中未找到Token: \n token:[{}] \n account:[{}]", token, account);
            session.close();
            return;
        }
        if (verificationService.checkUserByToken(account, token)) {
            String sGldUserID = redisTemplate.opsForHash().get(token, "gldUserId").toString();
            webSocketMap.put(account, session);
             logger.info("webSocket校验成功: \n token:[{}] \n account:[{}]", token, account);
        } else {
            logger.info("webSocket校验失败,: \n token:[{}] \n account:[{}]", token, account);
            session.close();
        }
        super.afterConnectionEstablished(session);
    }

    /**
     * 连接关闭调用的方法
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception
    {
        String account = getAttributeValue(session, "account");
        if(webSocketMap.containsKey(account)){
            webSocketMap.remove(account);
        }
        super.afterConnectionClosed(session, status);
    }

    /**
     * 实现服务器主动推送
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception
    {
        TextMessage msg=new TextMessage(message.getPayload());
        session.sendMessage(msg);
    }

    /**
     * 收到客户端消息后调用的方法
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception
    {
        super.handleMessage(session, message);
    }

    /**
     * 连接发生异常后触发的方法,关闭出错会话的连接,和删除在Map集合中的记录
     * @param session
     * @param exception
     * @throws Exception
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception
    {
        if (session.isOpen()) {
            session.close();
        }
        logger.error("连接出错");
        webSocketMap.remove(getAttributeValue(session, "account"));
        super.handleTransportError(session, exception);
    }

    /**
     * 获取session中的属性值
     * @param session
     * @param attribute
     * @return
     */
    private String getAttributeValue(WebSocketSession session, String attribute) {
        try {
            // 获取存入websocket的attributeValue
            String attributeValue = (String) session.getAttributes().get(attribute);
            return attributeValue;
        } catch (Exception e) {
            return null;
        }
    }
}

1.4实现ApplicationContextAware

package com.glodon.webSocket;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author lihongxiang
 * @ 2020/5/25 16:27
 */
@Component
public class SpringUtil implements ApplicationContextAware {

    //ApplicationContext对象是Spring开源框架的上下文对象实例,在项目运行时自动装载Handler内的所有信息到内存。
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取 Bean.
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

}

1.5调用接口类

package com.glodon.webSocket;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.socket.TextMessage;

/**
 * @author lihongxiang
 * @ 2020/5/8 17:36
 */
@Controller
@RequestMapping("/v2/edit/websocket")
public class CheckCenterController {

    @Autowired
    private WsHandler wsHandler;

    //推送数据接口
    @ResponseBody
    @GetMapping("/push/{account}")
    public String pushToWsHandler(@PathVariable String account) {
        try {
            wsHandler.handleMessage(WsHandler.webSocketMap.get(account), new TextMessage("招标方更新了招标清单"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return account;
    }
}

2.前端

2.1前端配置

/* * @Author: lihx * @Date: 2020-05-12 09:15:36 * @LastEditTime: 2020-05-26 19:31:41 * @LastEditors: lihx * @Description: webSocket请求相关 * @FilePath: \front\webbqFront\src\api\common\common.webSocket.js * @可以输入预定的版权声明、个性签名、空行等 */import storage from '../../sessionstorage/edit.storage.js';
var websock = null;function initWebSocket() { // 初始化websocket  if (typeof (WebSocket) == 'undefined') {    console.log('您的浏览器不支持WebSocket');  } else {    console.log('您的浏览器支持WebSocket');    let loginInfo = storage().getLoginInfo('account');    let account = loginInfo.account;    let token = loginInfo.token;    let wsuri = 'ws://127.0.0.1:9010/websocket?account=' + account + '&&token=' + token; // 加入token校验    websock = new WebSocket(wsuri);    console.log(websock);    websock.onopen = websocketopen;    websock.onmessage = websocketonmessage;    websock.onclose = websocketclose;    websock.onerror = websocketerror;  }  return websock;}
function websocketopen() { // 打开  console.log('WebSocket连接成功');  alert('WebSocket连接成功');}
function websocketonmessage(e) { // 数据接收  console.log(e.data);  alert(e.data);}
function websocketclose() { // 关闭  console.log('WebSocket关闭');  alert('WebSocket关闭');}
function websocketerror() { // 失败  console.log('WebSocket连接失败');  alert('WebSocket连接失败');}
// 发送数据function send(websock, data) {  // 此处先判断socket连接状态  switch (websock.readyState) {    // CONNECTING:值为0,表示正在连接。    case websock.CONNECTING:      console.log('WebSocket连接失败');      alert('WebSocket连接失败');      break;    // OPEN:值为1,表示连接成功,可以通信了。    case websock.OPEN:      console.log('WebSocket可以通信');      alert('WebSocket已发送消息');      websock.send(data);      break;    // CLOSING:值为2,表示连接正在关闭。    case websock.CLOSING:      console.log('WebSocket正在关闭');      alert('WebSocket正在关闭,无法发送消息');      break;    // CLOSED:值为3,表示连接已经关闭,或者打开连接失败。    case websock.CLOSED:      // do something      break;    default:      // this never happens      break;  }}
// 关闭websocketfunction close(websock, onSuccess, onError) {  websock.close();}
export default function webSocket() {  return {    initWebSocket,    send,    close  };};

2.2前端使用

在根组件 App.vue 中create方法中接收消息

import workerUtils from './service/edit/common/edit.workerUtils.js';
export default {  components: {    'v-header': Header,    'v-new-header': qbHeader,    'v-checkAnalysisNav': checkAnalysisNav    // 'v-footer': Footer,  },  data: function() {    return {      totalAmount: 0,      projectName: '广联达企业清单编制文件',      openFromCef: window.open_source_fromcef,      curPro: {},      webSocket: null    };  },  created: function () {    let vueObj = this;    bus.$on('updateFooterProjectTotal', function (newTotal) {      vueObj.totalAmount = newTotal;    });    bus.$on('updateFooterProjectName', function (newName) {      vueObj.projectName = newName;    });    bus.$on('webSocket-open', function (project) {      vueObj.webSocket = webSocket().initWebSocket(project.tenderInfo.tenderID);    });    bus.$on('webSocket-close', function () {      webSocket().close(vueObj.webSocket);    });    bus.$on('webSocket-send', function (message) {      webSocket().send(vueObj.webSocket, message);    });    projectManager().read(function(data) {      vueObj.curPro = projectManager().getCurProject();      if (vueObj.curPro.type == 'tb') {        projectManager().getCurrentProjectLocker(vueObj.curPro._id, vueObj.curPro.type, function (opener) {          if (opener.data !== 'noLocker') {            vueObj.webSocket = webSocket().initWebSocket(vueObj.curPro.tenderInfo.tenderID);          }        });      }    });    vueObj.registerWindowFunc();  },
}