Cometd, Spring-security: аутентифицированный в данный момент пользователь недоступен в Listener
Я работаю над проектом Spring-MVC, где я использую Spring-security для аутентификации и авторизации. Наряду с этим я использую библиотеку Cometd для отправки сообщений через веб-сокеты. После получения сообщения в Слушателе, когда я пытаюсь получить аутентифицированного пользователя, оно всегда равно нулю.
Как я могу убедиться, что каждый запрос в Cometd по крайней мере содержит JSESSIONID, который требуется Spring-security для идентификации?
Или в Spring-security есть настройки, которые могут сделать это возможным.
Как я посмотрел, есть много пользователей, сталкивающихся с этой проблемой, но нет точного ответа или кода, который был бы полезен.
Тестовый код:
@Listener(value = "/service/testlistener/{id}")
public void testlistener(ServerSession remote,
ServerMessage message, @Param("id") String id) {
try {
Person user = this.personService.getCurrentlyAuthenticatedUser();
Map<String, Object> data = message.getDataAsMap();
System.out.println("currentlyAuthenticatedUser: " + user + " \nTransmitted Data in map:" + data.get("name"));
Map<String, Object> output = new HashMap<>();
output.put("name", user.getFirstName());
ServerChannel serverChannel = bayeux.createChannelIfAbsent("/person/" + id).getReference();
serverChannel.setPersistent(false);
serverChannel.publish(serverSession, output);
} catch (Exception e) {
e.printStackTrace();
}
}
cometd.js:
var connectionIntervall = null;
var onlineElement = document.getElementById("browser-online");
var cometd = $.cometd;
cometd.configure({
url: navigationController.generateCometDUrl(),
logLevel: 'error',
stickyReconnect: false,
appendMessageTypeToURL: false,
requestHeaders : navigationController.generateCometDHeaders()
});
var connects = 0;
cometd.addListener('/meta/handshake', _metaHandshake);
cometd.addListener('/meta/connect', _metaConnect);
cometd.websocketEnabled = true;
cometd.handshake();
0 ответов
Решение для: - CometD 4.0.2 - Spring 4.3.9.RELEASE - Spring Security 4.1.0.RELEASE
В моем проекте мы столкнулись с подобной проблемой при переходе от соединения с "длинным пулом" к соединению "веб-сокет". Для пояснения - одно отличие - мы использовали "пользовательский контекст" только во время "рукопожатия".
С использованием "длинного пула" Cookie с JSESSIONID были правильно интерпретированы Spring, и мы работали в "пользовательском контексте". После переключения на "websocket" это выглядело так, как будто "пользовательский контекст" был потерян непосредственно перед запуском кода получения сообщений.
Мы не смогли найти правильное решение - следующий код, на мой взгляд, просто взломать. Но это сработало для нас.
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.bayeux.server.ServerSession;
import org.cometd.server.DefaultSecurityPolicy;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
public class BayeuxAuthenticator extends DefaultSecurityPolicy implements ServerSession.RemoveListener {
// (...)
@Override
public boolean canHandshake( BayeuxServer server, ServerSession session, ServerMessage message ) {
SecurityContextHolder.getContext().setAuthentication( ( UsernamePasswordAuthenticationToken ) message.getBayeuxContext().getUserPrincipal() );
String user = securityService.getUserName();
// (...)
return true;
}
// (...)
}
Где следующая строка является наиболее важной
SecurityContextHolder.getContext().setAuthentication( ( UsernamePasswordAuthenticationToken ) message.getBayeuxContext().getUserPrincipal() );
Изменить 2:
Мы получаем данные от
String user = securityService.getUserName();
где securityService - это Spring @Service, защищенный Spring @Secured(/* ожидаемые роли */). (Аннотация @Secured создавала нам исключения авторизации перед настройкой контекста)
Внутри есть контекстное чтение и пользовательские данные:
SecurityContextHolder.getContext().getUserPrincipal().getName()
Но, если честно, если вам просто нужны "принципалы пользователя", может быть достаточно прочитать его из:
message.getBayeuxContext().getUserPrincipal() // instanceof java.security.Principal
Который в нашем случае был типа
org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Может в твоем случае это другой подкласс?
Для пояснения - при запуске соединения через веб-сокет мы передали Cookie с правильным JSESSIONID в запросе.
Запрос подключения WebSocket:
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,pl;q=0.8
Cache-Control: no-cache
Connection: Upgrade
Cookie: XSRF-TOKEN=<some token>; Idea-<some token>; lastOpenTab=sourcing/content; io=<some token>; BAYEUX_BROWSER=<some token>; JSESSIONID=<some token>
Host: localhost:8090
Origin: http://localhost:8090
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: NTtWtXAL8ReIB0LjvI2G0g==
Sec-WebSocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 ...