Spring WebSockets STOMP 代理模式集成 RabbitMQ

环境

java 11
gradle
spring boot 2.1.1
RabbitMQ 服务
需要STOMP插件安装
如果你需要定时消息,需要安装 rabbitmq_delayed_message_exchange
插件


简要说明

  1. 依赖包安装:

     implementation ('org.springframework.boot:spring-boot-starter-thymeleaf')
     
     implementation('org.springframework.boot:spring-boot-starter-security') // 安全模块
     implementation('org.springframework.security:spring-security-messaging')
     
     implementation('org.springframework.boot:spring-boot-starter-websocket') // websocket
     
     implementation('org.springframework.session:spring-session-data-redis')// spring session data redis 已支持认证多项目共享
     implementation('io.lettuce:lettuce-core')
    
  2. 代理设置类SessionSocketsStompConfig

       @Configuration
       @EnableWebSocketMessageBroker
       public class SessionSocketsStompConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<Session> {
       
           /**
            * 设置一个SockJS端点链接
            * /socket
            * setAllowedOrigins 跨域设置
            *
            * @param registry 端点注册器
            */
           public void configureStompEndpoints(StompEndpointRegistry registry) {
               registry.addEndpoint("/socket").setAllowedOrigins("*").withSockJS();
           }
       
           /**
            * 消息规则,可以理解成消息 订阅地址前缀的
            * <p>
            * 总要 (red) registry.setPathMatcher(new AntPathMatcher("."));
            * 因为 rabbitmq是不支持URL "/" 分隔符,所以我们要自定义它的分隔符为 ".",如果不定义就会报错.
            * 比如订阅 /amq/queue/delayed.trade 过期订单
            * <p>
            * .setUserDestinationBroadcast("/topic/unresolved.user.destination")
            * .setUserRegistryBroadcast("/topic/registry.broadcast")
            * 多项目设置,比如A 端点给 B端点用户发送,需要设置的
            * <p>
            * 参考
            * <a>https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#websocket-stomp-enable</a>
            *
            * @param registry 端点设置
            */
           @Override
           public void configureMessageBroker(MessageBrokerRegistry registry) {
       
               registry.setPathMatcher(new AntPathMatcher("."));
       
               registry.enableStompBrokerRelay("/topic", "/queue", "/exchange", "/amq/queue")
                       // 其他服务访问桥连,一但这里没有找到可以去这里推送给其他用户
                       .setUserDestinationBroadcast("/topic/unresolved.user.destination")
                       .setUserRegistryBroadcast("/topic/registry.broadcast")
                       .setAutoStartup(true);
               registry.setApplicationDestinationPrefixes("/app");
               registry.setPreservePublishOrder(true);
           }
       }
    

    AbstractSessionWebSocketMessageBrokerConfigurer启用Session的管理,它很简单就是管理用户认证后Session的生命周期.

  3. Sockets安全配置类SocketSecurityConfig

       public class SocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
       
       
           @Autowired
           private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
       
           /**
            * 因为我们采用spring-session 头x-auth-token 认证
            * 那么传统的web认证就无法,获取到消息认证信息了.
            * 所以我们要从它的session获取认证信息手动增加到消息上.
            * 这样消息就具有认证信息了.
            * 我们可以参考spring
            * https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#websocket-stomp-authentication-token-based
            *
            * @param registration 消息请求体
            */
           @Override
           protected void customizeClientInboundChannel(ChannelRegistration registration) {
       
               registration.interceptors(new ChannelInterceptor() {
                   @Override
                   public Message<?> preSend(Message<?> message, MessageChannel channel) {
       
                       // 获取消息头
                       StompHeaderAccessor accessor =
                               MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
       
                       // 获取 x-auth-token session id
                       List<String> sessions = accessor != null ? accessor.getNativeHeader("x-auth-token") : null;
       
                       if (sessions == null) return message;
       
                       //根据 session id 获取它的认证信息
                       Session session = sessionRepository.findById(sessions.get(0));
                       if (session == null) return message;
       
                       //根据 session id 获取它的认证信息 SPRING_SECURITY_CONTEXT
                       SecurityContext securityContext = session.getAttribute("SPRING_SECURITY_CONTEXT");
       
                       if (securityContext == null) return message;
       
                       //根据 SPRING_SECURITY_CONTEXT  获取用户的认证信息 Authentication
                       Authentication user = securityContext.getAuthentication();
       
                       //判断用户是否已认证,一般获取到的是已认证的用户,也就是只要存在,用户肯定被认证了.
                       //但是 如果你设置了用户过期,没有设置清理session 用户还是存在的.这时候就必须去判断用户的认证状态,不然会报无权限错误
                       if (user.isAuthenticated()) {
       
                           // 重要这里 我们获取的认证信息,手动赋值给它.
                           accessor.setUser(user);
       
                           /*根据spring session sockets API 将x-auth-token 放入sockets 会话
                              让spring sockets 代理托管我们的session 会话生命周期.若果用户没有断开链接
                              session 是不会过期的具体查看 @SessionSocketsStompConfig 代码*/
                           Map<String, Object> attributes = accessor.getSessionAttributes();
                           if (attributes != null) {
                               attributes.put("SPRING.SESSION.ID", sessions.get(0));
                               accessor.setSessionAttributes(attributes);
                           }
                       } else {
                           throw new BadCredentialsException("Your authentication information has expired please login again.");
                       }
                       return message;
                   }
               });
           }
       
           /**
            * sockets 权限消息
            *
            * @param messages 消息体
            */
           @Override
           protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
               messages
                       .simpSubscribeDestMatchers("/user/queue/errors", "").permitAll()
                       .simpDestMatchers("/app/**").hasRole("USER")
                       .simpSubscribeDestMatchers("/user/**", "/queue/**").hasRole("USER");
           }
       
           /**
            * 关闭Sockets 的CSRF 保护
            *
            * @return true
            */
           @Override
           protected boolean sameOriginDisabled() {
               return true;
           }
       
       }
    

关于

我没有写客户端实现,由于时间问题,暂时没有实现.
这是一个最基本的代理服务链接RabbitMQ的实现,它可以跨域,也集成了安全,重要的知识点都涵盖了.
关于定时这个消息是rabbitmq_delayed_message_exchange这个插件实现的,后面我会详细介绍.

对于不明白的可以加我QQ询问.

示例代码 github gitee

作者

云舒 QQ:5199840

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355