【问题标题】:Principal is null for every Spring websocket event每个 Spring websocket 事件的 Principal 为 null
【发布时间】:2019-06-07 15:14:34
【问题描述】:

我正在尝试从 Spring websocket SessionConnectEvent 获取 Principal 用户名,但它在每个侦听器上都为 null。我可能做错了什么?

为了实现它,我遵循了您将在此处找到的答案:how to capture connection event in my webSocket server with Spring 4?

@Slf4j
@Service
public class SessionEventListener {

    @EventListener
    private void handleSessionConnect(SessionConnectEvent event) {
        SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
        String sessionId = headers.getSessionId();
        log.debug("sessionId is " + sessionId);
        String username = headers.getUser().getName(); // headers.getUser() is null
        log.debug("username is " + username);
    }

    @EventListener
    private void handleSessionConnected(SessionConnectEvent event) {
        SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
        String sessionId = headers.getSessionId();
        log.debug("sessionId is " + sessionId);
        String username = headers.getUser().getName(); // headers.getUser() is null
        log.debug("username is " + username);
    }

    @EventListener
    private void handleSubscribeEvent(SessionSubscribeEvent event) {
        SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
        String sessionId = headers.getSessionId();
        log.debug("sessionId is " + sessionId);
        String subscriptionId = headers.getSubscriptionId();
        log.debug("subscriptionId is " + subscriptionId);
        String username = headers.getUser().getName(); // headers.getUser() is null
        log.debug("username is " + username);
    }

    @EventListener
    private void handleUnsubscribeEvent(SessionUnsubscribeEvent event) {
        SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
        String sessionId = headers.getSessionId();
        log.debug("sessionId is " + sessionId);
        String subscriptionId = headers.getSubscriptionId();
        log.debug("subscriptionId is " + subscriptionId);
        String username = headers.getUser().getName(); // headers.getUser() is null
        log.debug("username is " + username);
    }

    @EventListener
    private void handleSessionDisconnect(SessionDisconnectEvent event) {
        SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
        log.debug("sessionId is " + event.getSessionId());
        String username = headers.getUser().getName(); // headers.getUser() is null
        log.debug("username is " + username);
    }

}

这是我的安全配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .permitAll()
            .and().csrf().disable();
    }
}

【问题讨论】:

    标签: java spring-boot websocket spring-websocket stomp


    【解决方案1】:

    由于我没有实现身份验证机制,因此 Spring 没有足够的信息来提供 Principal 用户名。所以我要做的就是配置一个 HandshakeHandler 来生成 Principal。

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
        public static final String ENDPOINT_CONNECT = "/connect";
        public static final String SUBSCRIBE_USER_PREFIX = "/private";
        public static final String SUBSCRIBE_USER_REPLY = "/reply";
        public static final String SUBSCRIBE_QUEUE = "/queue";
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry registry) {
            registry.enableSimpleBroker(SUBSCRIBE_QUEUE, SUBSCRIBE_USER_REPLY);
            registry.setUserDestinationPrefix(SUBSCRIBE_USER_PREFIX);
        }
    
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            registry.addEndpoint(ENDPOINT_CONNECT)
                    // assign a random username as principal for each websocket client
                    // this is needed to be able to communicate with a specific client
                    .setHandshakeHandler(new AssignPrincipalHandshakeHandler())
                    .setAllowedOrigins("*");
        }
    
    }
    
    /**
     * Assign a random username as principal for each websocket client. This is
     * needed to be able to communicate with a specific client.
     */
    public class AssignPrincipalHandshakeHandler extends DefaultHandshakeHandler {
        private static final String ATTR_PRINCIPAL = "__principal__";
    
        @Override
        protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
            final String name;
            if (!attributes.containsKey(ATTR_PRINCIPAL)) {
                name = generateRandomUsername();
                attributes.put(ATTR_PRINCIPAL, name);
            } else {
                name = (String) attributes.get(ATTR_PRINCIPAL);
            }
            return new Principal() {
                @Override
                public String getName() {
                    return name;
                }
            };
        }
    
        private String generateRandomUsername() {
            RandomStringGenerator randomStringGenerator = 
                    new RandomStringGenerator.Builder()
                        .withinRange('0', 'z')
                        .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS).build();
            return randomStringGenerator.generate(32);
        }
    }
    

    【讨论】:

      【解决方案2】:

      查看AbstractSubProtocolEvent(您感兴趣的所有事件中的superclass)的实现,您可以看到用户被保存在一个单独的字段中。因此,您可以通过调用event.getUser() 来访问用户。您不需要从消息中获取它。

      例如对于SessionConnectedEvent,您可以看到用户在事件中被填充,但不是消息。

      更新:

      您只有在对 http 升级进行身份验证后才能访问该用户。所以你需要有一个 WebSecurityConfigurerAdapter 来配置类似的东西:

      @Configuration
      public static class UserWebSecurity extends WebSecurityConfigurerAdapter {
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.requestMatchers()
                      .antMatchers(WebsocketPaths.WEBSOCKET_HANDSHAKE_PREFIX); //You configured the path in WebSocketMessageBrokerConfigurer#registerStompEndpoints
      
              http
                      .authorizeRequests()
                      .anyRequest().authenticated();
          }
      
      }
      

      【讨论】:

      • 更新了我的答案。当您没有用户时,您可能会在握手时缺少身份验证。
      • 我用您建议的具有安全配置的代码更新了我的问题。也不行。
      • 我认为你的配置有误,因为你调用permitAll() 会禁用身份验证。
      • 那么,主体用户名是由认证设置的?
      • 没错。您需要在用户和您的请求之间建立某种连接,这就是身份验证的用途。因此,您需要确保在建立 websocket 连接时进行身份验证,这就是您的情况下的握手。
      猜你喜欢
      • 2016-12-21
      • 2020-04-15
      • 2020-10-28
      • 1970-01-01
      • 1970-01-01
      • 2018-09-30
      • 1970-01-01
      • 2018-07-02
      • 2021-10-02
      相关资源
      最近更新 更多