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校验错误则会立即断开连接,点击右边的箭头可以查看具体异常信息

相关推荐
AIFQuant19 小时前
贵金属 API 避坑:黄金/白银行情接口常见陷阱(数据漂移、断点、延迟)
开发语言·python·websocket·金融·restful·贵金属
Jul1en_21 小时前
【SpringCloud】OpenFeign 与 Gateway 讲解与部署
spring·spring cloud·gateway
AI精钢1 天前
修复 AI Gateway 图片 MIME 类型错误:用魔数检测替代扩展名猜测
网络·人工智能·python·gateway·aigc
Qt程序员2 天前
从协议到实战:HTTP 反向代理
linux·c++·websocket·nginx·http·反向代理·正向代理
专注VB编程开发20年2 天前
轻量级多进程消息收发模型WEBSOCKET,MQTT
网络·websocket·网络协议
Ribou3 天前
Cilium Gateway API 配置手册
gateway
kels88993 天前
WebSocket 汇率数据:如何剔除过期行情
网络·websocket·网络协议
无所事事O_o3 天前
基于netty的websocket服务优化
java·websocket·netty·优化
YJlio4 天前
OpenClaw v2026.4.8 更新解析:扩展加载修复、通道配置优化、Slack 代理支持与升级避坑
gateway·自动化运维·版本更新·ai agent·openclaw·slack·插件兼容
链上杯子4 天前
WebSocket 和 SSE 怎么选?实时通信入门与避坑
网络·websocket·网络协议