Я пытаюсь получить имя основного пользователя из 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();
}
}




Глядя на реализацию Аннотация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();
}
}
Я обновил свой вопрос фрагментом кода с конфигурацией безопасности, как вы предложили. Тоже не работает.
Я думаю, что ваша конфигурация неверна, потому что вы вызываете permitAll(), который отключает аутентификацию.
Итак, основное имя пользователя устанавливается аутентификацией?
Точно. Вам нужно установить какую-то связь между пользователем и вашим запросом, и для этого нужна аутентификация. Поэтому вам нужно убедиться, что аутентификация происходит, когда вы устанавливаете соединение через веб-сокет, что в вашем случае является рукопожатием.
Но что, если я не хочу защищать свое подключение к веб-сокету? У весны должно быть что-то для этого случая.
Но как вам сопоставить пользователя, не зная, действительно ли он тот пользователь, за которого себя выдает? Что вы можете сделать, так это реализовать фильтр аутентификации, который просто «верит» пользователю по информации заголовка с именем пользователя. Недавно я написал об этом статью в блоге: code-held.com/2019/05/09/…
Поскольку я не реализую механизм аутентификации, 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);
}
}
Обновил мой ответ. Если у вас нет пользователя, вам, вероятно, не хватает аутентификации при рукопожатии.