Как получить/установить атрибуты принципала и сеанса из Spring 4 методов stomp websocket

Я делаю эксперименты с Spring 4 веб-разъемами и топами, и мне сложно определить, как получить/установить текущие пользовательские и другие атрибуты сеанса в методе обработки сообщений, аннотированные с помощью @MessageMapping.

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

Итак, мои вопросы:

  • Как вручную установить принципала в сеанс сокета, когда клиент подключается (у меня есть эта информация благодаря пользовательскому cookie, и я не использую Spring security)?
  • Если 1 невозможно, как добавить дополнительные атрибуты в сеанс сокета при подключении клиента?
  • Как получить доступ к сеансу сокета и его атрибутам из метода обработки сообщений?
  • Есть ли способ доступа к логину и паролю, отправленному браузером во время соединения. Они, кажется, полностью игнорируют Spring и не доступны.

Ответы

Ответ 2

ОБНОВЛЕНИЕ: С помощью Spring 4.1 можно установить пользователя на рукопожатие для # 1 сверху. Per документация Spring вы можете создать новый класс, который расширяет DefaultHandshakeHandler и переопределяет метод defineUser. Кроме того, вы также можете создать фильтр безопасности, который также устанавливает принципала, если у вас есть токен. Я сам реализовал второй, и я включил некоторый пример кода для обоих ниже.

Для # 2 и # 3 я не думаю, что это возможно. Для # 4 Spring намеренно игнорирует эти данные для документации здесь.

SAMPLE CODE для DefaultHandshakeHandler SUBCLASS:

@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();
    }
}

** ПРИМЕЧАНИЕ: ** НЕ объявляйте свой фильтр как Bean. Если вы это сделаете, он также будет поднят (по крайней мере, с помощью Spring Boot) в общих фильтрах, чтобы он срабатывал при каждом запросе.