【问题标题】:How to get/set the principal and session attributes from Spring 4 stomp websocket methods如何从 Spring 4 stomp websocket 方法获取/设置主体和会话属性
【发布时间】:2014-01-02 03:51:08
【问题描述】:

我正在使用Spring 4 websockets and stomp 进行实验,我很难弄清楚如何在使用@MessageMapping 注释的消息处理方法中获取/设置当前用户和其他会话属性。

The documentation 说消息处理方法可以接受一个Principal作为参数,我发现这个principal是Spring通过在本机socket session上调用getUserPrincipal()来检索的,然后与socket session相关联,但是我除了编写一个 servlet 过滤器并将原始请求包装到一个包装器中,返回在我的 cookie 中找到的主体之外,还没有找到任何轻松自定义此行为的方法。

所以我的问题是:

  1. 如何在客户端连接时手动将主体设置为套接字会话(由于自定义 cookie,我有此信息,并且我不使用 Spring 安全性)?
  2. 如果不能为1,客户端连接时如何给socket session添加额外的属性?
  3. 如何通过消息处理方法访问套接字会话及其属性?
  4. 有没有办法在连接时访问浏览器发送的登录名和密码。它们似乎被 Spring 完全忽略并且无法访问。

【问题讨论】:

    标签: spring websocket stomp spring-messaging spring-websocket


    【解决方案1】:

    更新:使用 Spring 4.1,可以将用户设置为从上面的 #1 握手。根据the Spring documentation,您可以创建一个扩展 DefaultHandshakeHandler 的新类并覆盖 determineUser 方法。此外,如果您有令牌,您还可以创建一个设置主体的安全过滤器。我自己实现了第二个,并在下面包含了一些示例代码。

    对于#2 和#3,我认为这仍然是不可能的。对于#4 Spring 有意忽略这些the documentation here

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

    ** 注意: ** 不要将您的过滤器声明为 Bean。如果你这样做了,那么它也会在通用过滤器中被拾取(至少使用 Spring Boot),因此它会在每个请求时触发。

    【讨论】:

      【解决方案2】:

      这暂时是不可能的(Spring 4.0)。 Spring 已打开(并考虑)了一个问题:https://jira.springsource.org/browse/SPR-11228

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-17
        • 1970-01-01
        相关资源
        最近更新 更多