1. 序章

前の記事で、SpringMVCプロジェクトにWebSocketを追加する方法を示しました。

ここでは、SpringMVCのSpringWebSocketにセキュリティを追加する方法について説明します。 続行する前に、基本的なSpring MVCセキュリティカバレッジがすでに設定されていることを確認してください。そうでない場合は、この記事を確認してください。

2. Mavenの依存関係

WebSocketの実装に必要なMaven依存関係には2つの主要なグループがあります。

まず、使用するSpringフレームワークとSpringセキュリティの包括的なバージョンを指定しましょう。

<properties>
    <spring.version>5.3.13</spring.version>
    <spring-security.version>5.7.3</spring-security.version>
</properties>

次に、基本認証と承認を実装するために必要なコアSpringMVCおよびSpringセキュリティライブラリを追加しましょう。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${spring-security.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring-security.version}</version>
</dependency>

spring-core spring-web spring-webmvc spring-security-web springの最新バージョン-security-configはMavenCentralにあります。

最後に、必要な依存関係を追加しましょう。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-messaging</artifactId>
    <version>${spring-security.version}</version>
</dependency>

spring-websocket spring-messaging 、およびspring-security-messagingの最新バージョンはMavenCentralで見つけることができます。

3. 基本的なWebSocketセキュリティ

spring-security-messaging ライブラリを使用したWebSocket固有のセキュリティは、AbstractSecurityWebSocketMessageBrokerConfigurerクラスとプロジェクト内でのその実装を中心としています。

@Configuration
public class SocketSecurityConfig 
  extends AbstractSecurityWebSocketMessageBrokerConfigurer {
      //...
}

AbstractSecurityWebSocketMessageBrokerConfigurer クラスは、WebSecurityConfigurerAdapter。によって提供される追加のセキュリティカバレッジを提供します。

spring-security-messaging ライブラリは、WebSocketのセキュリティを実装する唯一の方法ではありません。 通常のspring-websocketライブラリを使用する場合は、 WebSocketConfigurer インターフェイスを実装し、セキュリティインターセプターをソケットハンドラーにアタッチできます。

spring-security-messaging ライブラリを使用しているため、AbstractSecurityWebSocketMessageBrokerConfigurerアプローチを使用します。

3.1. configureInbound()の実装

configureInbound()の実装は、AbstractSecurityWebSocketMessageBrokerConfigurerサブクラスを構成する上で最も重要なステップです。

@Override 
protected void configureInbound(
  MessageSecurityMetadataSourceRegistry messages) { 
    messages
      .simpDestMatchers("/secured/**").authenticated()
      .anyMessage().authenticated(); 
}

WebSecurityConfigurerAdapter では、さまざまなルートのさまざまなアプリケーション全体の承認要件を指定できますが、 AbstractSecurityWebSocketMessageBrokerConfigurer では、ソケットの宛先に特定の承認要件を指定できます。

3.2. タイプと宛先のマッチング

MessageSecurityMetadataSourceRegistry を使用すると、パス、ユーザーロール、許可されるメッセージなどのセキュリティ制約を指定できます。

タイプマッチャーは、どのSimpMessageTypeが許可されるかおよびどのようにを制限します

.simpTypeMatchers(CONNECT, UNSUBSCRIBE, DISCONNECT).permitAll()

デスティネーションマッチャーは、アクセス可能なエンドポイントパターンを制限しますそしてどのように

.simpDestMatchers("/app/**").hasRole("ADMIN")

サブスクライブ先マッチャーは、SimpMessageType.SUBSCRIBE:に一致する SimpDestinationMessageMatcher i nstancesのリストをマップします。

.simpSubscribeDestMatchers("/topic/**").authenticated()

これは、タイプと宛先のマッチングで使用可能なすべてのメソッドの完全なリストです。

4. ソケットルートの保護

基本的なソケットセキュリティとタイプマッチング構成を紹介したので、ソケットセキュリティ、ビュー、STOMP(テキストメッセージングプロトコル)、メッセージブローカー、およびソケットコントローラーを組み合わせて、SpringMVCアプリケーション内で安全なWebSocketを有効にすることができます。 。

まず、基本的なSpringセキュリティカバレッジ用にソケットビューとコントローラを設定しましょう。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@EnableWebSecurity
@ComponentScan("com.baeldung.springsecuredsockets")
public class SecurityConfig { 
    @Bean 
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 
        http
          .authorizeRequests()
          .antMatchers("/", "/index", "/authenticate").permitAll()
          .antMatchers(
            "/secured/**/**",
            "/secured/success", 
            "/secured/socket",
            "/secured/success").authenticated()
          .anyRequest().authenticated()
          .and()
          .formLogin()
          .loginPage("/login").permitAll()
          .usernameParameter("username")
          .passwordParameter("password")
          .loginProcessingUrl("/authenticate")
          //...
    }
}

次に、認証要件を使用して実際のメッセージの宛先を設定しましょう。

@Configuration
public class SocketSecurityConfig 
  extends AbstractSecurityWebSocketMessageBrokerConfigurer {
    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages
          .simpDestMatchers("/secured/**").authenticated()
          .anyMessage().authenticated();
    }   
}

これで、 WebSocketMessageBrokerConfigurerで、実際のメッセージとSTOMPエンドポイントを登録できます。

@Configuration
@EnableWebSocketMessageBroker
public class SocketBrokerConfig 
  implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/secured/history");
        config.setApplicationDestinationPrefixes("/spring-security-mvc-socket");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/secured/chat")
          .withSockJS();
    }
}

上記のセキュリティカバレッジを提供したサンプルソケットコントローラーとエンドポイントを定義しましょう。

@Controller
public class SocketController {
 
    @MessageMapping("/secured/chat")
    @SendTo("/secured/history")
    public OutputMessage send(Message msg) throws Exception {
        return new OutputMessage(
           msg.getFrom(),
           msg.getText(), 
           new SimpleDateFormat("HH:mm").format(new Date())); 
    }
}

5. 同一生成元ポリシー

同一生成元ポリシーでは、エンドポイントとのすべての対話が、対話が開始されたのと同じドメインから行われる必要があります。

たとえば、WebSocketの実装が foo.com でホストされており、同一生成元ポリシーを適用しているとします。 ユーザーがfoo.comでホストされているクライアントに接続し、 bar.com で別のブラウザーを開いた場合、bar.comはあなたのWebSocketの実装。

5.1. 同一生成元ポリシーのオーバーライド

Spring WebSocketは、そのままで同一生成元ポリシーを適用しますが、通常のWebSocketは適用しません。

実際、 Spring Securityでは、有効な CONNECT メッセージタイプに対してCSRF(クロスサイトリクエストフォージェリ)トークンが必要です。

@Controller
public class CsrfTokenController {
    @GetMapping("/csrf")
    public @ResponseBody String getCsrfToken(HttpServletRequest request) {
        CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        return csrf.getToken();
    }
}

/ csrf でエンドポイントを呼び出すことにより、クライアントはトークンを取得し、CSRFセキュリティレイヤーを介して認証できます。

ただし、 AbstractSecurityWebSocketMessageBrokerConfigurer に次の構成を追加することで、Springの同一生成元ポリシーをオーバーライドできます

@Override
protected boolean sameOriginDisabled() {
    return true;
}

5.2. STOMP、SockJSサポート、およびフレームオプション

STOMPSockJSと一緒に使用して、SpringWebSocketのクライアント側サポートを実装するのが一般的です。

SockJSは、デフォルトでHTMLiframe要素を介した転送を許可しないように構成されています。 これは、クリックジャッキングの脅威を防ぐためです

ただし、iframesがSockJSトランスポートを利用できるようにすることが有益な場合があります。 To do so, you can create SecurityFilterChain bean:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) 
  throws Exception {
    http
      .csrf()
        //...
        .and()
      .headers()
        .frameOptions().sameOrigin()
      .and()
        .authorizeRequests();
    return http.build();
}

この例では、 iframes を介した転送を許可しているにもかかわらず、同一生成元ポリシーに従うことに注意してください。

6. Oauth2カバレッジ

Oauth2-specific support for Spring WebSockets is made possible by implementing Oauth2 security coverage in addition to — and by extending — your standard WebSecurityConfigurerAdapter coverage. これが Oauth2を実装する方法の例。

認証してWebSocketエンドポイントにアクセスするには、クライアントからバックエンドWebSocketに接続するときに、Oauth2 access_tokenをクエリパラメーターに渡すことができます。

SockJSとSTOMPを使用してその概念を示す例を次に示します。

var endpoint = '/ws/?access_token=' + auth.access_token;
var socket = new SockJS(endpoint);
var stompClient = Stomp.over(socket);

7. 結論

この簡単なチュートリアルでは、SpringWebSocketにセキュリティを追加する方法を示しました。 この統合について詳しく知りたい場合は、SpringのWebSocketおよびWebSocketSecurityのリファレンスドキュメントを参照してください。

いつものように、この記事で使用されている例については、Githubプロジェクトを確認してください。