security整合websocket

发布时间 2023-09-11 18:06:19作者: letfly

快速使用

 @Configuration
 @EnableWebSocketMessageBroker
 public class WebSocketSecurityConfig
         extends AbstractSecurityWebSocketMessageBrokerConfigurer {
     @Autowired
     UserDetailsServiceImpl userDetailsService;
 
     @Autowired
     RedisTemplate<String,Object> redisTemplate;
 
     @Override
     public void registerStompEndpoints(StompEndpointRegistry registry) {
         registry.addEndpoint("/ws/chat").setAllowedOriginPatterns("*").withSockJS();
    }
 
     @Override
     public void configureMessageBroker(MessageBrokerRegistry registry) {
         registry.enableSimpleBroker("/queue");
    }
     @Override
     protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
         messages.simpSubscribeDestMatchers("/queue").permitAll()
        .simpDestMatchers("/ws/chat").permitAll();
    }
 
     
     @Override
     protected boolean sameOriginDisabled() {
         return true; // 禁用 CSRF 验证
    }
 
 
 
 
 }

一开始继承AbstractSecurityWebSocketMessageBrokerConfigurer类之后,发现无论如何都要经过CSRF拦截,建立连接总会被拦截。经过排查代码发现此类中重写的方法如下

 @Override
 public final void configureClientInboundChannel(ChannelRegistration registration) {
    ChannelSecurityInterceptor inboundChannelSecurity = this.context.getBean(ChannelSecurityInterceptor.class);
    registration.setInterceptors(this.context.getBean(SecurityContextChannelInterceptor.class));
     
    if (!sameOriginDisabled()) {
       registration.setInterceptors(this.context.getBean(CsrfChannelInterceptor.class));
    }
    if (this.inboundRegistry.containsMapping()) {
       registration.setInterceptors(inboundChannelSecurity);
    }
    customizeClientInboundChannel(registration);
 }

发现sameOriginDisabled是默认写死

 /**
  * <p>
  * Determines if a CSRF token is required for connecting. This protects against remote
  * sites from connecting to the application and being able to read/write data over the
  * connection. The default is false (the token is required).
  * </p>
  * <p>
  * Subclasses can override this method to disable CSRF protection
  * </p>
  * @return false if a CSRF token is required for connecting, else true
  */
 /**
 *该方法用于确定是否需要CSRF令牌进行连接。这样可以保护应用免受远程站点连接并能够通过连接读取/写入数据的攻击。默认值为false(需要令牌)。
 *子类可以覆盖此方法以禁用CSRF保护。
 *返回值:
 *如果需要CSRF令牌进行连接,则返回false,否则返回true。
 */
 protected boolean sameOriginDisabled() {
    return false;
 }

所以要在子类中覆写这个方法 返回true,然后就绕过了同源策略

本人这里不懂为什么要继承这个类,网上搜是可以用来做身份默认校验,但是我如果用自定义的手段,这里的代码意义就不是很大了。比如我现在token是在请求头里传过来,然后要去redis里面去找。

 @Configuration
 @EnableWebSocketMessageBroker
 public class WebScoketConfig implements WebSocketMessageBrokerConfigurer {
     @Autowired
     RedisTemplate<String,Object> redisTemplate;
 
     @Autowired
     UserDetailsServiceImpl userDetailsService;
     
     @Override
     public void configureClientInboundChannel(ChannelRegistration registration) {
         registration.interceptors(new ChannelInterceptor() {
             @Override
             public Message<?> preSend(Message<?> message, MessageChannel channel) {
                 StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                 if(StompCommand.CONNECT.equals(accessor.getCommand())){
                     System.out.println("进行验证----------------------------------------");
                     String token = accessor.getFirstNativeHeader("Authorization");
                     if (StrUtil.isBlank(token)) {
                         throw new PasswordExpiredException("未携带token");
                    }
                     Authentication authentication = (Authentication) redisTemplate.opsForValue().get(RedisConstant.AUTHENTICATION_CACHE_KEY_PREFIX + token);
                     if (ObjectUtil.isEmpty(authentication))
                         throw new PasswordExpiredException("token错误或失效,请重新登录");
 
                     userDetailsService.loadUserByUsername(authentication.getName());
                     accessor.setUser(authentication);
                }
                 return message;
            }
        });
    }
 }

然后就可以开一个controller接口用来建立链接了

 @Controller
 public class UserChatController {
 
     @Autowired
     SimpMessagingTemplate messagingTemplate;
 
     Logger logger = LoggerFactory.getLogger(UserChatController.class);
     @MessageMapping("/ws/chat")
     @SendTo("/queue")
     public void handleChat(Authentication authentication, ChatMsg msg) {
         System.out.println("执行到这里");
         String name = authentication.getName();
         msg.setFrom(name);
         System.out.println(name);
         System.out.println(msg.getTo());
         messagingTemplate.convertAndSendToUser(msg.getTo(),"/queue/chat",msg);
    }
 }