gateway + websocket 实现权限校验

添加websocket的依赖

xml 复制代码
<!-- SpringBoot Websocket -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

修改默认的数据传输大小及会话超时

java 复制代码
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    @Bean
    public ServletServerContainerFactoryBean servletServerContainerFactoryBean() {
        ServletServerContainerFactoryBean factoryBean = new ServletServerContainerFactoryBean();
        // 1M
        factoryBean.setMaxTextMessageBufferSize(1024 * 1024);
        factoryBean.setMaxBinaryMessageBufferSize(1024 * 1024);
        // 30 分钟
        factoryBean.setMaxSessionIdleTimeout(1000 * 60 * 30L);
        return factoryBean;
    }
}

websocket服务端

java 复制代码
@Slf4j
@Component
@ServerEndpoint(value = "/websocket/{clientId}", encoders = {WebSocketObjectEncoder.class})
public class WebSocketHandler {
	private RedisService redisService = SpringUtils.getBean(RedisService.class);
    private static final ConcurrentMap<String, Session> SESSION_MAP = new ConcurrentHashMap<>();
    private String clientId;

    @OnOpen
    public void onOpen(Session session, @PathParam("clientId") String clientId) throws IOException {
        String sessionId = session.getId();
        log.info("onOpen sessionId: {}, clientId: {}", sessionId, clientId);
        this.clientId = clientId;
        // 增加权限校验
        boolean res = this.validateToken(session.getRequestParameterMap());
        if (!res) {
        	// 未校验通过直接断开
            session.close(new CloseReason(CloseReason.CloseCodes.PROTOCOL_ERROR, "非法请求"));
            return;
        }
        SESSION_MAP.put(clientId, session);
        this.sendText(session, String.format("客户端【%s】已连接", clientId));
    }

    @OnMessage
    public void onMessage(Session session, @PathParam("clientId") String clientId, String message) {
        String sessionId = session.getId();
        log.info("onMessage sessionId:{}, clientId:{}, message:{}", sessionId, clientId, message);
        this.sendText(session, String.format("客户端【%s】消息已收到", clientId));
    }

    @OnClose
    public void onClose(Session session) {
        log.info("sessionId-{} onClose ...", session.getId());
        SESSION_MAP.remove(this.clientId);
    }

    @OnError
    public void onError(Session session, Throwable throwable) throws IOException {
        log.error("Error for session " + session.getId() + " : " + throwable.getMessage());
        if (session.isOpen()) {
            session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage()));
        }
        SESSION_MAP.remove(this.clientId);
    }

    /**
     * 检验客户端身份
     *
     * @param params
     * @return
     */
    private boolean validateToken(Map<String, List<String>> params) {
        try {
            boolean res = true;
            String token = params.getOrDefault("token", new ArrayList<>()).get(0);
            if (StringUtils.isBlank(token)) {
                res = false;
            }
            Claims claims = JwtUtils.parseToken(token);
            String jwt_claims_key = "user_id";
            if (claims == null || !claims.containsKey(jwt_claims_key)) {
                res = false;
            }
            // RedisKey.USER_LOGIN_TOKEN = "user_login_token:%s"
            String redisKey = String.format(RedisKey.USER_LOGIN_TOKEN, JwtUtils.getUserId(claims));
            if(!redisService.hasKey(redisKey)){
            	res = false;
            }
            return res;
        } catch (Exception e) {
            log.info("WebSocket token validate error: {}", e.getMessage());
        }
        return false;
    }

    /**
     * 发送消息,需要对Object进行序列化,所以WebSocketObjectEncoder是必须的
     * 或者直接在这里使用JSONObject.toJSONString(obj) 也是可以的
     *
     * @param clientId
     * @param data
     */
    public void sendMessage(String clientId, Object data) {
        try {
            if (SESSION_MAP.containsKey(clientId)) {
                SESSION_MAP.get(clientId).getBasicRemote().sendObject(data);
            }
        } catch (Exception e) {
            log.error("sendMessage error:{}", e.getMessage());
        }
    }

    /**
     * 发送文本消息
     *
     * @param session
     * @param message
     */
    private void sendText(Session session, String message) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

序列化工具 WebSocketObjectEncoder

java 复制代码
public class WebSocketObjectEncoder implements Encoder.Text<Object> {

    @Override
    public String encode(Object obj) throws EncodeException {
        return JSONObject.toJSONString(obj);
    }

    @Override
    public void init(EndpointConfig endpointConfig) {

    }

    @Override
    public void destroy() {

    }
}

在gateway中配置路由

yaml 复制代码
spring:
  cloud:
    gateway:
      discovery:
        locator:
          lowerCaseServiceId: true
          enabled: false
      routes:
        # WEBSOCKET服务
        - id: websocket
          uri: lb:ws://websocket
          predicates: 
            - Path=/websocket/**

# 安全配置
security:
  # 不校验白名单
  ignore:
    whites:
      - /websocket/**

测试

使用Postman,点击左上角的Menu - File - New...

找到WebSocket

url中填写ws://localhost/websocket/{clientId}?token={token},如果是使用ssl证书的域名,则填写wss://www.xxx.com/websocket/{clientId}?token={token}

如果能正常接收到服务端返回的消息说明连接成功

如果token校验错误则会立即断开连接,点击右边的箭头可以查看具体异常信息

相关推荐
xrkhy20 小时前
websocket(即时通讯)
网络·websocket·网络协议
ZKshun1 天前
WebSocket指南:从原理到生产环境实战
前端·websocket
wuk9981 天前
实现ROS系统的Websocket传输,向Web应用推送sensor_msgs::Image数据
前端·websocket·网络协议
C2H5OH6662 天前
Netty详解-02
java·websocket·网络协议·tcp/ip·tomcat·netty·nio
半旧夜夏2 天前
【Gateway】服务调用和网关配置攻略
java·spring boot·spring cloud·gateway
诚实可靠王大锤2 天前
WebSocket调试工具(html),用于调试WebSocket链接是否畅通
websocket·网络协议·html
小坏讲微服务2 天前
Nginx集群与SpringCloud Gateway集成Nacos的配置指南
spring boot·nginx·spring cloud·gateway
小坏讲微服务2 天前
使用 Spring Cloud Gateway 实现集群
java·spring boot·分布式·后端·spring cloud·中间件·gateway
没有bug.的程序员2 天前
Spring Cloud Gateway 路由与过滤器机制
java·开发语言·spring boot·spring·gateway
星光一影2 天前
SpringBoot+Vue3无人机AI巡检系统
人工智能·spring boot·websocket·mysql·intellij-idea·mybatis·无人机