Intro to Security and WebSockets – 安全和WebSockets简介

最后修改: 2017年 7月 11日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In a previous article, we showed how to add WebSockets to a Spring MVC project.

前一篇文章中,我们展示了如何将WebSockets添加到Spring MVC项目中。

Here, we’ll describe how to add security to Spring WebSockets in Spring MVC. Before continuing, make sure you already have basic Spring MVC Security coverage in place – if not, check out this article.

在此,我们将介绍如何在Spring MVC中为Spring WebSockets添加安全性。在继续之前,请确保您已经有了基本的Spring MVC安全保障 – 如果没有,请查看这篇文章

2. Maven Dependencies

2.Maven的依赖性

There are two main groups of Maven dependencies we need for our WebSocket implementation.

我们的WebSocket实现需要两组主要的Maven依赖项。

First, let’s specify the overarching versions of the Spring Framework and Spring Security that we will be using:

首先,让我们指定我们将使用的Spring框架和Spring Security的总体版本。

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

Second, let’s add the core Spring MVC and Spring Security libraries required to implement basic authentication and authorization:

其次,让我们添加实现基本认证和授权所需的Spring MVC和Spring Security核心库。

<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>

The latest versions of spring-core, spring-web, spring-webmvc, spring-security-web, spring-security-config can be found on Maven Central.

spring-corespring-webspring-webmvcspring-security-webspring-security-config的最新版本可以在Maven中心找到。

Lastly, let’s add the required dependencies:

最后,让我们添加所需的依赖项。

<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>

You can find the latest version of spring-websocket, spring-messaging, and spring-security-messaging on Maven Central.

您可以在Maven中心找到最新版本的spring-websocketspring-messagingspring-security-messaging

3. Basic WebSocket Security

3.基本的WebSocket安全

WebSocket-specific security using the spring-security-messaging library centers on the AbstractSecurityWebSocketMessageBrokerConfigurer class and its implementation within your project:

使用spring-security-messaging库的WebSocket特定安全性集中于AbstractSecurityWebSocketMessageBrokerConfigurer类及其在项目中的实现。

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

The AbstractSecurityWebSocketMessageBrokerConfigurer class provides additional security coverage provided by WebSecurityConfigurerAdapter.

AbstractSecurityWebSocketMessageBrokerConfigurer提供了由WebSecurityConfigurerAdapter提供的额外安全覆盖

The spring-security-messaging library is not the only way to implement security for WebSockets. If we stick with the ordinary spring-websocket library, we can implement the WebSocketConfigurer interface and attach security interceptors to our socket handlers.

spring-security-messaging库并不是为WebSockets实现安全的唯一途径。如果我们坚持使用普通的spring-websocket库,我们可以实现WebSocketConfigurer接口,并将安全拦截器附加到我们的套接字处理程序。

Since we are using the spring-security-messaging library, we will use the AbstractSecurityWebSocketMessageBrokerConfigurer approach.

由于我们正在使用spring-security-messaging库,我们将使用AbstractSecurityWebSocketMessageBrokerConfigurer方法。

3.1. Implementing configureInbound()

3.1.实现configureInbound()

The implementation of configureInbound() is the most important step in configuring your AbstractSecurityWebSocketMessageBrokerConfigurer subclass:

实现 configureInbound()是配置AbstractSecurityWebSocketMessageBrokerConfigurer子类的最重要步骤。

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

Whereas the WebSecurityConfigurerAdapter lets you specify various application-wide authorization requirements for different routes, AbstractSecurityWebSocketMessageBrokerConfigurer allows you to specify the specific authorization requirements for socket destinations.

WebSecurityConfigurerAdapter让你为不同的路由指定各种应用范围的授权要求,而AbstractSecurityWebSocketMessageBrokerConfigurer允许你为套接字目的地指定具体的授权要求。

3.2. Type and Destination Matching

3.2.类型和目的地匹配

MessageSecurityMetadataSourceRegistry allows us to specify security constraints like paths, user roles, and which messages are allowed.

MessageSecurityMetadataSourceRegistry允许我们指定安全约束,如路径、用户角色,以及允许哪些消息。

Type matchers constrain which SimpMessageType are allowed and in what way:

类型匹配器限制了哪些SimpMessageType被允许,以及以何种方式:。

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

Destination matchers constrain which endpoint patterns are accessible and in what way:

目的地匹配器制约了哪些端点模式可以访问以及以何种方式访问:。

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

Subscribe destination matchers map a List of SimpDestinationMessageMatcher instances that match on SimpMessageType.SUBSCRIBE:

订阅目的地匹配器映射一个List SimpDestinationMessageMatcher instances,在SimpMessageType.SUBSCRIBE:上匹配。

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

Here is the complete list of all available methods for type and destination matching.

这里是类型和目标匹配的所有可用方法的完整列表。

4. Securing Socket Routes

4.确保套接字路由的安全

Now that we’ve been introduced to basic socket security and type matching configuration, we can combine socket security, views, STOMP (a text-messaging protocol), message brokers, and socket controllers to enable secure WebSockets within our Spring MVC application.

现在我们已经介绍了基本的套接字安全和类型匹配配置,我们可以将套接字安全、视图、STOMP(一种文本消息协议)、消息代理和套接字控制器结合起来,在Spring MVC应用中实现安全的WebSockets。

First, let’s set up our socket views and controllers for basic Spring Security coverage:

首先,让我们为基本的Spring安全覆盖设置我们的socket视图和控制器。

@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")
          //...
    }
}

Second, let’s set up the actual message destination with authentication requirements:

第二,让我们设置实际的消息目的地与认证要求。

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

Now, in our WebSocketMessageBrokerConfigurer, we can register the actual message and STOMP endpoints:

现在,在我们的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();
    }
}

Let’s define an example socket controller and endpoint that we provided security coverage for above:

让我们定义一个套接字控制器和端点的例子,我们在上面提供了安全覆盖。

@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. Same Origin Policy

5.同源政策

The Same Origin Policy requires that all interactions with an endpoint must come from the same domain where the interaction was initiated.

同源政策要求与一个端点的所有互动必须来自启动互动的同一个域。

For example, suppose your WebSockets implementation is hosted at foo.com, and you are enforcing same origin policy. If a user connects to your client hosted at foo.com and then opens another browser to bar.com, then bar.com will not have access to your WebSocket implementation.

例如,假设您的WebSockets实现是托管在foo.com,并且您正在执行同源策略。如果用户连接到托管在foo.com的客户端,然后打开另一个浏览器到bar.com,那么bar.com将无法访问你的WebSocket实现。

5.1. Overriding the Same Origin Policy

5.1.覆盖同源政策

Spring WebSockets enforce the Same Origin Policy out of the box, while ordinary WebSockets do not.

Spring WebSockets开箱即执行同源政策,而普通WebSockets则没有。

In fact, Spring Security requires a CSRF (Cross Site Request Forgery) token for any valid CONNECT message type:

事实上,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();
    }
}

By calling the endpoint at /csrf, a client can acquire the token and authenticate through the CSRF security layer.

通过调用/csrf的端点,客户端可以获得令牌并通过CSRF安全层进行认证。

However, the Same Origin Policy for Spring can be overridden by adding the following configuration to your AbstractSecurityWebSocketMessageBrokerConfigurer:

然而,可以通过在AbstractSecurityWebSocketMessageBrokerConfigurer中添加以下配置来覆盖Spring的同源策略

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

5.2. STOMP, SockJS Support, and Frame Options

5.2.STOMP、SockJS支持和框架选项

It is common to use STOMP along with SockJS to implement client-side support for Spring WebSockets.

通常使用STOMPSockJS来实现Spring WebSockets的客户端支持。

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

SockJS被配置为默认不允许通过HTML iframe元素进行传输。这是为了防止点击劫持的威胁

However, there are certain use-cases where allowing iframes to leverage SockJS transports can be beneficial. To do so, you can create SecurityFilterChain bean:

然而,在某些用例中,允许iframes利用SockJS传输可能是有益的。要做到这一点,你可以创建SecurityFilterChain bean。

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

Note that in this example, we follow the Same Origin Policy despite allowing transports through iframes.

请注意,在这个例子中,尽管我们允许通过iframe进行传输,但还是遵循同源政策

6. Oauth2 Coverage

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. Here’s an example of how to implement Oauth2.

通过在您的标准WebSecurityConfigurerAdapter coverage之外实现Oauth2安全覆盖,可以实现对Spring WebSockets的特定支持。下面是一个如何实现Oauth2的示例。

To authenticate and gain access to a WebSocket endpoint, you can pass an Oauth2 access_token into a query parameter when connecting from your client to your back-end WebSocket.

为了验证和获得对WebSocket端点的访问,在从客户端连接到后端WebSocket时,你可以将Oauth2 access_token传入查询参数。

Here’s an example demonstrating that concept using SockJS and STOMP:

下面是一个使用SockJS和STOMP演示这一概念的例子。

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

7. Conclusion

7.结论

In this brief tutorial, we have shown how to add security to Spring WebSockets. Take a look at Spring’s WebSocket and WebSocket Security reference documentation to if you are looking to learn more about this integration.

在这个简短的教程中,我们展示了如何向Spring WebSockets添加安全性。如果您想了解有关此集成的更多信息,请查看 Spring 的 WebSocketWebSocket 安全 参考文档。

As always, check our Github project for examples used in this article.

一如既往,请查看我们的Github项目,了解本文中使用的例子。