Introduction à la sécurité et aux WebSockets

Introduction à la sécurité et aux WebSockets

1. introduction

Dans unprevious article, nous avons montré comment ajouter des WebSockets à un projet Spring MVC.

Ici, nous allons décrire commentadd security to Spring WebSockets in Spring MVC. Avant de continuer, assurez-vous que vous disposez déjà de la couverture de base de Spring MVC Security - sinon, consultezthis article.

2. Dépendances Maven

Nous avons besoin detwo main groups of Maven dependencies pour notre implémentation WebSocket.

Tout d'abord, spécifions les versions globales de Spring Framework et Spring Security que nous utiliserons:


    5.0.6.RELEASE
    5.0.6.RELEASE

Deuxièmement, ajoutons les bibliothèques principales Spring MVC et Spring Security requises pour implémenter l'authentification et l'autorisation de base:


    org.springframework
    spring-core
    ${spring.version}


    org.springframework
    spring-web
    ${spring.version}


    org.springframework
    spring-webmvc
    ${spring.version}


    org.springframework.security
    spring-security-web
    ${spring-security.version}


    org.springframework.security
    spring-security-config
    ${spring-security.version}

Les dernières versions despring-core,spring-web,spring-webmvc,spring-security-web,spring-security-config sont disponibles sur Maven Central.

Enfin, ajoutons les dépendances requises:


    org.springframework
    spring-websocket
    ${spring.version}


    org.springframework
    spring-messaging
    ${spring.version}


    org.springframework.security
    spring-security-messaging
    ${spring-security.version}

Vous pouvez trouver la dernière version despring-websocket,spring-messaging etspring-security-messaging sur Maven Central.

3. Sécurité WebSocket de base

La sécurité spécifique à WebSocket utilisant la bibliothèquespring-security-messaging est centrée sur la classeAbstractSecurityWebSocketMessageBrokerConfigurer et son implémentation dans votre projet:

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

La classeAbstractSecurityWebSocketMessageBrokerConfigurerprovides additional security coverage fournie parWebSecurityConfigurerAdapter.

La bibliothèquespring-security-messaging n'est pas le seul moyen d'implémenter la sécurité pour les WebSockets. Si nous nous en tenons à la bibliothèque ordinairespring-websocket, nous pouvons implémenter l'interfaceWebSocketConfigurer et attacher des intercepteurs de sécurité à nos gestionnaires de sockets.

Puisque nous utilisons la bibliothèquespring-security-messaging, nous utiliserons l'approcheAbstractSecurityWebSocketMessageBrokerConfigurer.

3.1. Implémentation deconfigureInbound()

L'implémentation deconfigureInbound() est l'étape la plus importante dans la configuration de votre sous-classeAbstractSecurityWebSocketMessageBrokerConfigurer:

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

Alors queWebSecurityConfigurerAdapter vous permet de spécifier diverses exigences d'autorisation à l'échelle de l'application pour différentes routes,AbstractSecurityWebSocketMessageBrokerConfigurer vous permet de spécifier les exigences d'autorisation spécifiques pour les destinations de socket.

3.2. Correspondance de type et de destination

MessageSecurityMetadataSourceRegistry nous permet de spécifier des contraintes de sécurité telles que les chemins, les rôles d'utilisateur et les messages autorisés.

Type matchers constrain which SimpMessageType are allowed et de quelle manière:

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

Destination matchers constrain which endpoint patterns are accessible et de quelle manière:

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

Subscribe destination matchers map a ListofSimpDestinationMessageMatcher iubstancesthat match on SimpMessageType.SUBSCRIBE:

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

4. Sécurisation des routes de socket

Maintenant que nous avons été familiarisés avec la sécurité de base des sockets et la configuration de correspondance de type, nous pouvons combiner la sécurité des sockets, les vues, STOMP (un protocole de messagerie texte), les courtiers de messages et les contrôleurs de sockets pour activer des WebSockets sécurisés dans notre application Spring MVC.

Commençons par configurer nos vues de socket et nos contrôleurs pour la couverture de base de Spring Security:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@EnableWebSecurity
@ComponentScan("com.example.springsecuredsockets")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(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")
          //...
    }
}

Deuxièmement, configurons la destination réelle du message avec les exigences d'authentification:

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

Maintenant, dans nos WebSocketMessageBrokerConfigurer,, nous pouvons enregistrer le message réel et les points de terminaison 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();
    }
}

Définissonsan example socket controller et le point de terminaison pour lesquels nous avons fourni une couverture de sécurité ci-dessus:

@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. Politique de même origine

LeSame Origin Policy exige que toutes les interactions avec un point de terminaison doivent provenir du même domaine où l'interaction a été initiée.

Par exemple, supposons que votre implémentation WebSockets soit hébergée chezfoo.com et que vous soyezenforcing same origin policy. Si un utilisateur se connecte à votre client hébergé chezfoo.com puis ouvre un autre navigateur versbar.com, alorsbar.com n'aura pas accès à votre implémentation WebSocket.

5.1. Remplacement de la politique de même origine

Spring WebSockets applique la politique de même origine dès le départ, contrairement aux WebSockets ordinaires.

En fait,Spring Security requires a CSRF (Cross Site Request Forgery) token pour tout type de messageCONNECT valide:

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

En appelant le point de terminaison à/csrf, un client peut acquérir le jeton et s'authentifier via la couche de sécurité CSRF.

Cependant, lesSame Origin Policy for Spring can be overridden en ajoutant la configuration suivante à vosAbstractSecurityWebSocketMessageBrokerConfigurer:

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

5.2. STOMP, prise en charge SockJS et options de cadre

Il est courant d'utiliserSTOMP avecSockJS pour implémenter la prise en charge côté client pour Spring WebSockets.

SockJS is configured to disallow transports through HTML iframe elements by default. This is to prevent the threat of clickjacking.

Cependant, il existe certains cas d'utilisation où permettre àiframes de tirer parti des transports SockJS peut être bénéfique. Pour ce faire, vous pouvez remplacer la configuration par défaut dansWebSecurityConfigurerAdapter:

@Override
protected void configure(HttpSecurity http)
  throws Exception {
    http
      .csrf()
        //...
        .and()
      .headers()
        .frameOptions().sameOrigin()
      .and()
        .authorizeRequests();
}

Notez que dans cet exemple, nous suivons lesSame Origin Policy malgré l'autorisation des transports viaiframes.

6. Couverture Oauth2

La prise en charge spécifique à Oauth2 pour Spring WebSockets est rendue possible par l'implémentation de la couverture de sécurité Oauth2 en plus de - et en étendant - votre couvertureWebSecurityConfigurerAdapter standard.Here’s un exemple de mise en œuvre d'Oauth2.

Pour vous authentifier et accéder à un point de terminaison WebSocket, vous pouvez passer un Oauth2access_token dans un paramètre de requête lors de la connexion de votre client à votre WebSocket principal.

Voici un exemple illustrant ce concept à l'aide de SockJS et STOMP:

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

7. Conclusion

Dans ce bref tutoriel, nous avons montré comment ajouter de la sécurité à Spring WebSockets. Jetez un œil à la documentation de référence de Spring pourWebSocket etWebSocket Security si vous souhaitez en savoir plus sur cette intégration.

Comme toujours, vérifiezour Github project pour des exemples utilisés dans cet article.