Security和WebSocket简介

2023/05/17

1. 简介

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

在这里,我们将描述如何在 Spring MVC 中为 Spring WebSockets 添加安全性。在继续之前,请确保已经具备基本的 Spring MVC 安全覆盖——如果没有,请查看这篇文章

2.Maven依赖

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

首先,让我们指定我们将使用的 Spring Framework 和 Spring Security 的总体版本:

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

其次,让我们添加实现基本认证和授权所需的核心 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>

最新版本的[spring-core](https://search.maven.org/classic/#search gav 1 g%3A”org.springframework” AND a%3A”spring-core”)、[spring-web](https://search.maven.org/classic/#search ga 1 g%3A”org.springframework” AND a%3A”spring-web”)、[spring-webmvc](https://search.maven.org/classic/#search ga 1 g%3A”org.springframework” AND a%3A”spring-webmvc”)、[spring-security-web](https://search.maven.org/classic/#search ga 1 g%3A” org.springframework” AND a%3A”spring-security-web”)、[spring-security-config](https://search.maven.org/classic/#search ga 1 g%3A” org.springframework” AND a%3A”spring-security-config”)可以在 Maven Central 上找到。

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

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

可以在 Maven Central 上找到最新版本的[spring-websocket](https://search.maven.org/classic/#search ga 1 g%3A”org.springframework” AND a%3A”spring-websocket”)、[spring-messaging](https://search.maven.org/classic/#search ga 1 g%3A”org.springframework” AND a%3A”spring-messaging”)和[spring-security-messaging 。](https://search.maven.org/classic/#search ga 1 g%3A”org.springframework” AND a%3A”spring-security-messaging”)

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实例列表:

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

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

4. 保护套接字路由

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

首先,让我们为基本的 Spring Security 覆盖设置我们的套接字视图和控制器:

@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. 同源政策

同源策略要求与端点的所有交互都必须来自发起交互的同一域。

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

5.1。覆盖同源策略

Spring WebSockets 执行开箱即用的同源策略,而普通 WebSockets 没有。

事实上,对于任何有效的CONNECT消息类型, Spring Security 都需要一个 CSRF ( Cross Site Request Forgery ) 令牌:

@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来实现对 Spring WebSockets 的客户端支持。

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

但是,在某些用例中,允许iframe使用 SockJS 传输可能是有益的。为此,可以创建SecurityFilterChain bean:

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

请注意,在此示例中,尽管允许通过iframe进行传输,但我们仍遵循同源策略。

6. Oauth2 覆盖范围

通过在标准WebSecurityConfigurerAdapter 覆盖范围之外实现 Oauth2 安全覆盖,以及扩展 - 可以实现对 Spring WebSockets 的特定于 Oauth2 的支持 。这是一个如何实现 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. 总结

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

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章