Как получить / установить атрибуты principal и session из методов Spring 4 stomp websocket
Я провожу эксперименты с Весна 4 websockets и топать, и мне трудно понять, как получить / установить текущего пользователя и другие атрибуты сеанса в методе обработки сообщений с аннотацией @MessageMapping
.
документация говорит, что методы обработки сообщений могут принимать принципала в качестве аргумента, и я обнаружил, что принципал извлекается Spring, вызывая getUserPrincipal()
в сеансе собственного сокета, а затем связанный с сеанс сокета, но я не нашел способа легко настроить это поведение, кроме как написать фильтр сервлетов и обернуть исходный запрос в оболочку, возвращающую принципала, найденного в моем файле cookie.
Итак, мои вопросы:
- как вручную установить принципала в сеанс сокета, когда клиент подключается (у меня есть эта информация благодаря пользовательскому cookie, и я не использую Spring security)?
- если 1 невозможно, как добавить дополнительные атрибуты сеанса сокета при подключении клиента?
- как получить доступ к сеансу сокета и его атрибутам из метода обработки сообщений?
- есть ли способ получить доступ к логину и пароль, отправленные браузером в момент подключения. Они, кажется, полностью игнорируются весной и недоступны.
2 ответов
UPDATE: с Spring 4.1 можно установить пользователя на рукопожатие для #1 сверху. Пер Весенняя документация можно создать новый класс, который расширяет DefaultHandshakeHandler и переопределяет метод determineUser. Кроме того, вы также можете создать фильтр безопасности, который также устанавливает участника, если у вас есть токен. Я реализовал второй сам, и я включаю некоторый пример кода для обоих ниже.
для #2 и #3 я не думаю, что это еще возможно. Для # 4 Spring намеренно игнорирует эти per документация здесь.
пример кода для подкласса DefaultHandshakeHandler:
@Configuration
@EnableWebSocketMessageBroker
public class ApplicationWebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
public class MyHandshakeHandler extends DefaultHandshakeHandler {
@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
Map<String, Object> attributes) {
// add your own code to determine the user
return null;
}
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/myEndPoint").setHandshakeHandler(new MyHandshakeHandler());
}
}
ПРИМЕР КОДА ДЛЯ ФИЛЬТРА БЕЗОПАСНОСТИ:
public class ApplicationSecurityTokenFilter extends GenericFilterBean {
private final static String AUTHENTICATION_PARAMETER = "authentication";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest) {
// check to see if already authenticated before trying again
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if ((existingAuth == null) || !existingAuth.isAuthenticated()) {
HttpServletRequest request = (HttpServletRequest)servletRequest;
UsernamePasswordAuthenticationToken token = extractToken(request);
// dump token into security context (for authentication-provider to pick up)
if (token != null) { // if it exists
SecurityContextHolder.getContext().setAuthentication(token);
}
}
}
filterChain.doFilter(servletRequest,servletResponse);
}
private UsernamePasswordAuthenticationToken extractToken( HttpServletRequest request ) {
UsernamePasswordAuthenticationToken authenticationToken = null;
// do what you need to extract the information for a token
// in this example we assume a query string that has an authenticate
// parameter with a "user:password" string. A new UsernamePasswordAuthenticationToken
// is created and then normal authentication happens using this info.
// This is just a sample and I am sure there are more secure ways to do this.
if (request.getQueryString() != null) {
String[] pairs = request.getQueryString().split("&");
for (String pair : pairs) {
String[] pairTokens = pair.split("=");
if (pairTokens.length == 2) {
if (AUTHENTICATION_PARAMETER.equals(pairTokens[0])) {
String[] tokens = pairTokens[1].split(":");
if (tokens.length == 2) {
log.debug("Using credentials: " + pairTokens[1]);
authenticationToken = new UsernamePasswordAuthenticationToken(tokens[0], tokens[1]);
}
}
}
}
}
return authenticationToken;
}
}
// set up your web security for the area in question
@Configuration
public class SubscriptionWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/myEndPoint**","/myEndPoint/**").and()
.addFilterBefore(new ApplicationSecurityTokenFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic() // leave this if you want non web browser clients to connect and add an auth header
.and()
.csrf().disable();
}
}
**Примечание: * * не объявляйте фильтр в качестве компонента. Если вы это сделаете, он также будет подобран (по крайней мере, с помощью Spring Boot) в общих фильтрах, поэтому он будет срабатывать при каждом запросе.
Это невозможно в настоящее время (весна 4.0). Вопрос был открыт (и рассмотрен) весной:https://jira.springsource.org/browse/SPR-11228