Принципал равен нулю для каждого события весны websocket - PullRequest
0 голосов
/ 07 июня 2019

Я пытаюсь получить основное имя пользователя из Spring websocket SessionConnectEvent, но оно равно null для каждого слушателя. Что я могу делать не так?

Для его реализации я следовал ответам, которые вы найдете здесь: как перехватить событие подключения на моем сервере webSocket с 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();
    }
}

Ответы [ 2 ]

0 голосов
/ 10 июня 2019

Поскольку я не реализую механизм аутентификации, похоже, что в Spring недостаточно информации для предоставления основного имени пользователя (странно). Поэтому мне нужно было настроить HandshakeHandler, который генерирует принципал.

@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);
    }
}
0 голосов
/ 07 июня 2019

Рассматривая реализацию AbstractSubProtocolEvent (суперкласс всех интересующих вас событий), вы можете видеть, что пользователь удерживается в отдельном поле. Таким образом, вы можете просто получить доступ к пользователю, позвонив по номеру 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();
    }

}
...