Принципал равен нулю для каждого события веб-сокета Spring

Я пытаюсь получить имя основного пользователя из 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();
    }
}
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
0
2 689
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

}

Обновил мой ответ. Если у вас нет пользователя, вам, вероятно, не хватает аутентификации при рукопожатии.

Marcus Held 09.06.2019 11:03

Я обновил свой вопрос фрагментом кода с конфигурацией безопасности, как вы предложили. Тоже не работает.

abarazal 10.06.2019 16:57

Я думаю, что ваша конфигурация неверна, потому что вы вызываете permitAll(), который отключает аутентификацию.

Marcus Held 10.06.2019 18:37

Итак, основное имя пользователя устанавливается аутентификацией?

abarazal 10.06.2019 18:44

Точно. Вам нужно установить какую-то связь между пользователем и вашим запросом, и для этого нужна аутентификация. Поэтому вам нужно убедиться, что аутентификация происходит, когда вы устанавливаете соединение через веб-сокет, что в вашем случае является рукопожатием.

Marcus Held 10.06.2019 18:47

Но что, если я не хочу защищать свое подключение к веб-сокету? У весны должно быть что-то для этого случая.

abarazal 10.06.2019 18:49

Но как вам сопоставить пользователя, не зная, действительно ли он тот пользователь, за которого себя выдает? Что вы можете сделать, так это реализовать фильтр аутентификации, который просто «верит» пользователю по информации заголовка с именем пользователя. Недавно я написал об этом статью в блоге: code-held.com/2019/05/09/…

Marcus Held 10.06.2019 18:58
Ответ принят как подходящий

Поскольку я не реализую механизм аутентификации, Spring не имеет достаточно информации, чтобы предоставить основное имя пользователя. Итак, что мне нужно было сделать, так это настроить 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);
    }
}

Другие вопросы по теме

Spring Boot EHcache cacheResolver
Может ли дочерний pom наследовать управление плагинами и управление зависимостями от spring-boot-starter-parent пользовательского родительского pom?
Внешний клиент Camunda: TASK/CLIENT-02002 Исключение при установлении соединения для запроса
Spring boot, URL-адрес обратного вызова авторизации Oauth — настройка разработки и производства
Невозможно вставить строки в базу данных, используя данные Spring Batch + Spring
Как включить пользовательскую конечную точку привода для дочернего проекта?
Создайте объект HttpServletResponse в пользовательском фильтре Zuul
Приложение Spring boot, развернутое как война с tomcat7, завершается сбоем с ошибкой «Найдено более одного фрагмента с именем [org_apache_tomcat_websocket]»
Обслуживание статического контента приводит к тому, что метод запроса «GET» не поддерживается
Несколько источников данных JHipster вызывают JdbcSQLSyntaxErrorException: последовательность «SEQUENCEGENERATOR» не найдена