web-dev-qa-db-ja.com

Spring 4 stompwebsocketメソッドからプリンシパル属性とセッション属性を取得/設定する方法

Spring 4 websockets and stomp で実験を行っていますが、@MessageMappingアノテーションが付けられたメッセージ処理メソッドで現在のユーザーおよびその他のセッション属性を取得/設定する方法を理解するのに苦労しています。

ドキュメント は、メッセージ処理メソッドがプリンシパルを引数として取ることができると述べています。プリンシパルは、ネイティブソケットセッションでgetUserPrincipal()を呼び出して、Springによって取得され、関連付けられることがわかりました。ソケットセッションを使用しましたが、サーブレットフィルタを記述し、元のリクエストをラッパーにラップしてCookieで見つかったプリンシパルを返す以外に、この動作を簡単にカスタマイズする方法は見つかりませんでした。

だから私の質問は:

  1. クライアントが接続するときに、プリンシパルをソケットセッションに手動で設定するにはどうすればよいですか(カスタムCookieのおかげでこの情報があり、Springセキュリティを使用していません)?
  2. 1が不可能な場合、クライアントが接続するときにソケットセッションに属性を追加するにはどうすればよいですか?
  3. メッセージ処理メソッドからソケットセッションとその属性にアクセスするにはどうすればよいですか?
  4. 接続時にブラウザから送信されたログインとパスコードにアクセスする方法はありますか?それらはSpringによって完全に無視され、アクセスできないようです。
15
JB Nizet

これは当面は不可能です(Spring4.0)。 Springで問題が開かれました(検討されました): https://jira.springsource.org/browse/SPR-11228

4
JB Nizet

更新:Spring 4.1では、ユーザーを上から#1のハンドシェイクに設定することができます。 Springのドキュメント ごとに、DefaultHandshakeHandlerを拡張し、determineUserメソッドをオーバーライドする新しいクラスを作成できます。さらに、トークンがある場合は、プリンシパルを設定するセキュリティフィルターを作成することもできます。私は2番目のものを自分で実装し、以下の両方のサンプルコードをいくつか含めます。

#2と#3については、まだ可能だとは思いません。 #4の場合、Springは意図的にこれらを無視します ここのドキュメント

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を使用して)汎用フィルターでも取得されるため、すべてのリクエストで起動します。

6
Rob Baily