WebSocket Security

Spring Security 4 添加了对保护 Spring’s WebSocket support 的支持。本部分描述如何使用 Spring Security 的 WebSocket 支持。

Spring Security 4 added support for securing Spring’s WebSocket support. This section describes how to use Spring Security’s WebSocket support. .Direct JSR-356 Support

Spring Security 不提供直接的 JSR-356 支持,因为这样做几乎没有价值。这是因为该格式未知,且 little Spring can do to secure an unknown format。此外,JSR-356 没有提供一种拦截消息的方式,因此安全性将具有侵入性。

Spring Security does not provide direct JSR-356 support, because doing so would provide little value. This is because the format is unknown, and there is little Spring can do to secure an unknown format. Additionally, JSR-356 does not provide a way to intercept messages, so security would be invasive.

WebSocket Authentication

WebSocket 会在建立 WebSocket 连接时重复使用 HTTP 请求中找到的相同身份验证信息。这意味着 HttpServletRequest 中的 Principal 将被传递给 WebSocket。如果你正在使用 Spring Security,HttpServletRequest 中的 Principal 会自动被覆盖。

WebSockets reuse the same authentication information that is found in the HTTP request when the WebSocket connection was made. This means that the Principal on the HttpServletRequest will be handed off to WebSockets. If you are using Spring Security, the Principal on the HttpServletRequest is overridden automatically.

更具体地说,要确保用户已对你 WebSocket 应用程序进行身份验证,所有必需的操作就是确保你设置 Spring Security 以对基于 HTTP 的 Web 应用程序进行身份验证。

More concretely, to ensure a user has authenticated to your WebSocket application, all that is necessary is to ensure that you setup Spring Security to authenticate your HTTP based web application.

WebSocket Authorization

Spring Security 4.0 已通过 Spring Messaging 抽象层引入了对 WebSocket 的授权支持。

Spring Security 4.0 has introduced authorization support for WebSockets through the Spring Messaging abstraction.

在 Spring Security 5.8 中,此支持已刷新为使用 AuthorizationManager API。

In Spring Security 5.8, this support has been refreshed to use the AuthorizationManager API.

要使用 Java 配置配置授权,只需包含 `@EnableWebSocketSecurity`注释并发布 `AuthorizationManager<Message<?>>`bean 或在 XML中使用 `use-authorization-manager`属性。一种方法是使用 `AuthorizationManagerMessageMatcherRegistry`来指定端点模式,如下所示:

To configure authorization using Java Configuration, simply include the @EnableWebSocketSecurity annotation and publish an AuthorizationManager<Message<?>> bean or in XML use the use-authorization-manager attribute. One way to do this is by using the AuthorizationManagerMessageMatcherRegistry to specify endpoint patterns like so:

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableWebSocketSecurity // <1> 2
public class WebSocketSecurityConfig {

    @Bean
    AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
        messages
                .simpDestMatchers("/user/**").hasRole("USER") (3)

        return messages.build();
    }
}
@Configuration
@EnableWebSocketSecurity // <1> 2
open class WebSocketSecurityConfig { // <1> 2
    @Bean
    fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
        messages.simpDestMatchers("/user/**").hasRole("USER") (3)
        return messages.build()
    }
}
<websocket-message-broker use-authorization-manager="true"> 1 2
    <intercept-message pattern="/user/**" access="hasRole('USER')"/> 3
</websocket-message-broker>
1 Any inbound CONNECT message requires a valid CSRF token to enforce the websocket-sameorigin.
2 The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
3 Our messages require the proper authorization. Specifically, any inbound message that starts with /user/ will require ROLE_USER. You can find additional details on authorization in WebSocket Authorization

Custom Authorization

使用 AuthorizationManager 时,自定义非常简单。例如,你可以发布一个 AuthorizationManager,要求所有消息都使用 AuthorityAuthorizationManager 具有 “USER” 角色,如下所示:

When using AuthorizationManager, customization is quite simple. For example, you can publish an AuthorizationManager that requires that all messages have a role of "USER" using AuthorityAuthorizationManager, as seen below:

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableWebSocketSecurity // <1> 2
public class WebSocketSecurityConfig {

    @Bean
    AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
        return AuthorityAuthorizationManager.hasRole("USER");
    }
}
@Configuration
@EnableWebSocketSecurity // <1> 2
open class WebSocketSecurityConfig {
    @Bean
    fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
        return AuthorityAuthorizationManager.hasRole("USER") (3)
    }
}
<bean id="authorizationManager" class="org.example.MyAuthorizationManager"/>

<websocket-message-broker authorization-manager-ref="myAuthorizationManager"/>

有许多方法可以进一步匹配消息,如下面更高级的示例所示:

There are several ways to further match messages, as can be seen in a more advanced example below:

  • Java

  • Kotlin

  • Xml

@Configuration
public class WebSocketSecurityConfig {

    @Bean
    public AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
        messages
                .nullDestMatcher().authenticated() (1)
                .simpSubscribeDestMatchers("/user/queue/errors").permitAll() (2)
                .simpDestMatchers("/app/**").hasRole("USER") (3)
                .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") (4)
                .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() (5)
                .anyMessage().denyAll(); (6)

        return messages.build();
    }
}
@Configuration
open class WebSocketSecurityConfig {
    fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<*>> {
        messages
            .nullDestMatcher().authenticated() (1)
            .simpSubscribeDestMatchers("/user/queue/errors").permitAll() (2)
            .simpDestMatchers("/app/**").hasRole("USER") (3)
            .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") (4)
            .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() (5)
            .anyMessage().denyAll() (6)

        return messages.build();
    }
}
<websocket-message-broker use-authorization-manager="true">
    (1)
    <intercept-message type="CONNECT" access="permitAll" />
    <intercept-message type="UNSUBSCRIBE" access="permitAll" />
    <intercept-message type="DISCONNECT" access="permitAll" />

    <intercept-message pattern="/user/queue/errors" type="SUBSCRIBE" access="permitAll" /> (2)
    <intercept-message pattern="/app/**" access="hasRole('USER')" />      (3)

    (4)
    <intercept-message pattern="/user/**" type="SUBSCRIBE" access="hasRole('USER')" />
    <intercept-message pattern="/topic/friends/*" type="SUBSCRIBE" access="hasRole('USER')" />

    (5)
    <intercept-message type="MESSAGE" access="denyAll" />
    <intercept-message type="SUBSCRIBE" access="denyAll" />

    <intercept-message pattern="/**" access="denyAll" /> (6)
</websocket-message-broker>

这会确保:

This will ensure that:

1 Any message without a destination (i.e. anything other than Message type of MESSAGE or SUBSCRIBE) will require the user to be authenticated
2 Anyone can subscribe to /user/queue/errors
3 Any message that has a destination starting with "/app/" will be require the user to have the role ROLE_USER
4 Any message that starts with "/user/" or "/topic/friends/" that is of type SUBSCRIBE will require ROLE_USER
5 Any other message of type MESSAGE or SUBSCRIBE is rejected. Due to 6 we do not need this step, but it illustrates how one can match on specific message types.
6 Any other Message is rejected. This is a good idea to ensure that you do not miss any messages.

WebSocket Authorization Notes

为正确保护你的应用程序,你需要了解 Spring 的 WebSocket 支持。

To properly secure your application, you need to understand Spring’s WebSocket support.

WebSocket Authorization on Message Types

你需要了解 SUBSCRIBEMESSAGE 类型消息之间的区别以及它们如何在 Spring 中工作。

You need to understand the distinction between SUBSCRIBE and MESSAGE types of messages and how they work within Spring.

考虑聊天应用程序:

Consider a chat application:

  • The system can send a notification MESSAGE to all users through a destination of /topic/system/notifications.

  • Clients can receive notifications by SUBSCRIBE to the /topic/system/notifications.

虽然我们希望客户端能够 SUBSCRIBE/topic/system/notifications,但我们不想让它们能够向该目标发送 MESSAGE。如果我们允许向 /topic/system/notifications 发送 MESSAGE,则客户端可以将消息直接发送到该端点并冒充系统。

While we want clients to be able to SUBSCRIBE to /topic/system/notifications, we do not want to enable them to send a MESSAGE to that destination. If we allowed sending a MESSAGE to /topic/system/notifications, clients could send a message directly to that endpoint and impersonate the system.

一般来说,对于应用程序来说,拒绝发送到以 broker prefix (/topic//queue/) 开头的目的地的任何 MESSAGE 是很常见的。

In general, it is common for applications to deny any MESSAGE sent to a destination that starts with the broker prefix (/topic/ or /queue/).

WebSocket Authorization on Destinations

你应该了解目标是如何转变的。

You should also understand how destinations are transformed.

考虑聊天应用程序:

Consider a chat application:

  • Users can send messages to a specific user by sending a message to the /app/chat destination.

  • The application sees the message, ensures that the from attribute is specified as the current user (we cannot trust the client).

  • The application then sends the message to the recipient by using SimpMessageSendingOperations.convertAndSendToUser("toUser", "/queue/messages", message).

  • The message gets turned into the destination of /queue/user/messages-<sessionid>.

对于此聊天应用程序,我们希望允许客户端侦听 /user/queue,该消息被转换成了 /queue/user/messages-<sessionid>。但是,我们不希望客户端能够侦听 /queue/*,因为这样客户端就可以看到每个用户的消息。

With this chat application, we want to let our client to listen /user/queue, which is transformed into /queue/user/messages-<sessionid>. However, we do not want the client to be able to listen to /queue/*, because that would let the client see messages for every user.

一般来说,对于应用程序来说,拒绝发送到以 broker prefix (/topic//queue/) 开头的消息的任何 SUBSCRIBE 是很常见的。我们可能会提供例外,以考虑诸如此类的事情

In general, it is common for applications to deny any SUBSCRIBE sent to a message that starts with the broker prefix (/topic/ or /queue/). We may provide exceptions to account for things like

Outbound Messages

Spring Framework 参考文档包含一个名为 “Flow of Messages” 的部分,其中介绍了消息如何通过该系统传输。请注意,Spring Security 只保护 clientInboundChannel。Spring Security 不会尝试保护 clientOutboundChannel

The Spring Framework reference documentation contains a section titled “Flow of Messages” that describes how messages flow through the system. Note that Spring Security secures only the clientInboundChannel. Spring Security does not attempt to secure the clientOutboundChannel.

最主要的原因是性能。对于传入的每条消息,通常会有更多传出。我们提倡保护对这些端点的订阅,而不是保护传出的消息。

The most important reason for this is performance. For every message that goes in, typically many more go out. Instead of securing the outbound messages, we encourage securing the subscription to the endpoints.

Enforcing Same Origin Policy

请注意,浏览器不会为 WebSocket 连接强制执行 Same Origin Policy。这是一个极其重要的考虑因素。

Note that the browser does not enforce the Same Origin Policy for WebSocket connections. This is an extremely important consideration.

Why Same Origin?

考虑以下情景。用户访问 bank.com 并验证其帐户。同个用户在浏览器中打开另一个标签页并访问 evil.com。同源策略可确保 evil.com 无法读取 bank.com 中的数据或向其写入数据。

Consider the following scenario. A user visits bank.com and authenticates to their account. The same user opens another tab in their browser and visits evil.com. The Same Origin Policy ensures that evil.com cannot read data from or write data to bank.com.

采用 WebSocket 时,同源策略不适用。事实上,除非 bank.com 明确禁止,否则 evil.com 能够代表用户读取和写入数据。这意味着,用户可以通过 webSocket 执行的任何操作(例如转移资金),evil.com 都能代表其执行。

With WebSockets, the Same Origin Policy does not apply. In fact, unless bank.com explicitly forbids it, evil.com can read and write data on behalf of the user. This means that anything the user can do over the webSocket (such as transferring money), evil.com can do on that user’s behalf.

由于 SockJS 试图模拟 WebSocket,因此它也绕过了同源策略。这意味着,当开发人员使用 SockJS 时,需要明确保护其应用程序免遭外部域的侵害。

Since SockJS tries to emulate WebSockets, it also bypasses the Same Origin Policy. This means that developers need to explicitly protect their applications from external domains when they use SockJS.

Spring WebSocket Allowed Origin

幸运的是,由于 Spring 4.1.5 Spring 的 WebSocket 和 SockJS 支持限制了对 current domain 的访问。Spring Security 添加了一层额外的保护,以提供 defense in depth

Fortunately, since Spring 4.1.5 Spring’s WebSocket and SockJS support restricts access to the current domain. Spring Security adds an additional layer of protection to provide defense in depth.

Adding CSRF to Stomp Headers

默认情况下,Spring Security 在任何 `CONNECT`消息类型中都需要 CSRF token。这确保只有可以访问 CSRF 令牌的站点才能连接。由于只有 *same origin*可以访问 CSRF 令牌,因此不允许外部域进行连接。

By default, Spring Security requires the CSRF token in any CONNECT message type. This ensures that only a site that has access to the CSRF token can connect. Since only the same origin can access the CSRF token, external domains are not allowed to make a connection.

通常情况下,我们需要在 HTTP 标头或 HTTP 参数中包含 CSRF 令牌。但是,SockJS 不允许使用这些选项。相反,我们必须在 Stomp 标头中包含令牌。

Typically we need to include the CSRF token in an HTTP header or an HTTP parameter. However, SockJS does not allow for these options. Instead, we must include the token in the Stomp headers.

应用程序可以通过访问名为 _csrf`的请求属性来 obtain a CSRF token。例如,以下内容允许在 JSP 中访问 `CsrfToken

Applications can obtain a CSRF token by accessing the request attribute named _csrf. For example, the following allows accessing the CsrfToken in a JSP:

var headerName = "${_csrf.headerName}";
var token = "${_csrf.token}";

如果您使用静态 HTML,则可以在 REST 端点上公开 CsrfToken。例如,以下操作将在 /csrf URL 上公开 CsrfToken:

If you use static HTML, you can expose the CsrfToken on a REST endpoint. For example, the following would expose the CsrfToken on the /csrf URL:

  • Java

  • Kotlin

@RestController
public class CsrfController {

    @RequestMapping("/csrf")
    public CsrfToken csrf(CsrfToken token) {
        return token;
    }
}
@RestController
class CsrfController {
    @RequestMapping("/csrf")
    fun csrf(token: CsrfToken): CsrfToken {
        return token
    }
}

JavaScript 可以对该端点发出 REST 调用,并使用响应填充 headerName 和令牌。

The JavaScript can make a REST call to the endpoint and use the response to populate the headerName and the token.

现在,我们可以在 Stomp 客户端中包含令牌:

We can now include the token in our Stomp client:

...
var headers = {};
headers[headerName] = token;
stompClient.connect(headers, function(frame) {
  ...

})

Disable CSRF within WebSockets

考虑到这一点,当使用 @EnableWebSocketSecurity 时,CSRF 是不可配置的,不过这可能会在未来的版本中得到添加。

At this point, CSRF is not configurable when using @EnableWebSocketSecurity, though this will likely be added in a future release.

要禁用 CSRF,请使用 XML 支持或自行添加 Spring Security 组件,方法如下,而不是使用 @EnableWebSocketSecurity:

To disable CSRF, instead of using @EnableWebSocketSecurity, you can use XML support or add the Spring Security components yourself, like so:

  • Java

  • Kotlin

  • Xml

@Configuration
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        AuthorizationManager<Message<?>> myAuthorizationRules = AuthenticatedAuthorizationManager.authenticated();
        AuthorizationChannelInterceptor authz = new AuthorizationChannelInterceptor(myAuthorizationRules);
        AuthorizationEventPublisher publisher = new SpringAuthorizationEventPublisher(this.context);
        authz.setAuthorizationEventPublisher(publisher);
        registration.interceptors(new SecurityContextChannelInterceptor(), authz);
    }
}
@Configuration
open class WebSocketSecurityConfig : WebSocketMessageBrokerConfigurer {
    @Override
    override fun addArgumentResolvers(argumentResolvers: List<HandlerMethodArgumentResolver>) {
        argumentResolvers.add(AuthenticationPrincipalArgumentResolver())
    }

    @Override
    override fun configureClientInboundChannel(registration: ChannelRegistration) {
        var myAuthorizationRules: AuthorizationManager<Message<*>> = AuthenticatedAuthorizationManager.authenticated()
        var authz: AuthorizationChannelInterceptor = AuthorizationChannelInterceptor(myAuthorizationRules)
        var publisher: AuthorizationEventPublisher = SpringAuthorizationEventPublisher(this.context)
        authz.setAuthorizationEventPublisher(publisher)
        registration.interceptors(SecurityContextChannelInterceptor(), authz)
    }
}
<websocket-message-broker use-authorization-manager="true" same-origin-disabled="true">
    <intercept-message pattern="/**" access="authenticated"/>
</websocket-message-broker>

另一方面,如果您正在使用 legacy AbstractSecurityWebSocketMessageBrokerConfigurer 并且希望允许其他域访问您的网站,则可以禁用 Spring Security 的保护。例如,在 Java 配置中,您可以使用以下内容:

On the other hand, if you are using the legacy-websocket-configuration and you want to allow other domains to access your site, you can disable Spring Security’s protection. For example, in Java Configuration you can use the following:

  • Java

  • Kotlin

@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    ...

    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}
@Configuration
open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() {

    // ...

    override fun sameOriginDisabled(): Boolean {
        return true
    }
}

Custom Expression Handler

有时候,您可能会认为有必要自定义处理 intercept-message XML 元素中定义的 access 表达式的方式。要执行此操作,您可以创建一个类型为 SecurityExpressionHandler<MessageAuthorizationContext<?>> 的类,并像这样在 XML 定义中引用它:

At times, there may be value in customizing how the access expressions are handled defined in your intercept-message XML elements. To do this, you can create a class of type SecurityExpressionHandler<MessageAuthorizationContext<?>> and refer to it in your XML definition like so:

<websocket-message-broker use-authorization-manager="true">
    <expression-handler ref="myRef"/>
    ...
</websocket-message-broker>

<b:bean ref="myRef" class="org.springframework.security.messaging.access.expression.MessageAuthorizationContextSecurityExpressionHandler"/>

如果您要在 websocket-message-broker 实现 SecurityExpressionHandler<Message<?>> 的旧有用法,则您可以:1. 同时实现 createEvaluationContext(Supplier, Message) 方法,然后 2. 将该值包装在 MessageAuthorizationContextSecurityExpressionHandler 中,如下所示:

If you are migrating from a legacy usage of websocket-message-broker that implements a SecurityExpressionHandler<Message<?>>, you can: 1. Additionally implement the createEvaluationContext(Supplier, Message) method and then 2. Wrap that value in a MessageAuthorizationContextSecurityExpressionHandler like so:

<websocket-message-broker use-authorization-manager="true">
    <expression-handler ref="myRef"/>
    ...
</websocket-message-broker>

<b:bean ref="myRef" class="org.springframework.security.messaging.access.expression.MessageAuthorizationContextSecurityExpressionHandler">
    <b:constructor-arg>
        <b:bean class="org.example.MyLegacyExpressionHandler"/>
    </b:constructor-arg>
</b:bean>

Working with SockJS

SockJS 提供备用传输以支持旧版浏览器。在使用备用选项时,我们需要放宽一些安全限制,以允许 SockJS 与 Spring Security 配合使用。

SockJS provides fallback transports to support older browsers. When using the fallback options, we need to relax a few security constraints to allow SockJS to work with Spring Security.

SockJS & frame-options

SockJS 可能会使用 “@1”。默认情况下,Spring Security “@2” 该网站防止点击劫持攻击。为了允许基于 SockJS 框架的传输工作,我们需要配置 Spring Security 允许相同的原点填充内容。

SockJS may use a transport that leverages an iframe. By default, Spring Security denies the site from being framed to prevent clickjacking attacks. To allow SockJS frame-based transports to work, we need to configure Spring Security to let the same origin frame the content.

您可以使用 “@5” 元素来自定义 “@3”。例如,以下内容指示 Spring Security 使用 “@4”,它允许属于同一域内的 iframe:

You can customize X-Frame-Options with the frame-options element. For example, the following instructs Spring Security to use X-Frame-Options: SAMEORIGIN, which allows iframes within the same domain:

<http>
    <!-- ... -->

    <headers>
        <frame-options
          policy="SAMEORIGIN" />
    </headers>
</http>

同样地,您可以通过使用以下内容来使用 Java 配置定制框架选项,使其在同一域内:

Similarly, you can customize frame options to use the same origin within Java Configuration by using the following:

  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // ...
            .headers(headers -> headers
                .frameOptions(frameOptions -> frameOptions
                     .sameOrigin()
                )
        );
        return http.build();
    }
}
@Configuration
@EnableWebSecurity
open class WebSecurityConfig {
    @Bean
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            // ...
            headers {
                frameOptions {
                    sameOrigin = true
                }
            }
        }
        return http.build()
    }
}

SockJS & Relaxing CSRF

SockJS 对任何基于 HTTP 的传输在 CONNECT 消息上使用 POST。通常情况下,我们需要在 HTTP 标头或 HTTP 参数中包含 CSRF 令牌。但是,SockJS 不允许使用这些选项。相反,我们必须像 Adding CSRF to Stomp Headers 中所述,将其包含在 Stomp 标头中。

SockJS uses a POST on the CONNECT messages for any HTTP-based transport. Typically, we need to include the CSRF token in an HTTP header or an HTTP parameter. However, SockJS does not allow for these options. Instead, we must include the token in the Stomp headers as described in Adding CSRF to Stomp Headers.

这也意味着我们需要使用 Web 层放松 CSRF 保护。具体来说,我们希望为连接 URL 禁用 CSRF 保护。我们不希望为每个 URL 禁用 CSRF 保护。否则,我们的网站容易受到 CSRF 攻击。

It also means that we need to relax our CSRF protection with the web layer. Specifically, we want to disable CSRF protection for our connect URLs. We do NOT want to disable CSRF protection for every URL. Otherwise, our site is vulnerable to CSRF attacks.

我们可以通过提供 CSRF RequestMatcher 轻松实现这一点。我们的 Java 配置让这个变得简单。例如,如果我们的 stomp 终结点是 /chat,我们可以只使用以下配置,禁用从 /chat/ 开始的 URL 的 CSRF 保护:

We can easily achieve this by providing a CSRF RequestMatcher. Our Java configuration makes this easy. For example, if our stomp endpoint is /chat, we can disable CSRF protection only for URLs that start with /chat/ by using the following configuration:

  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                // ignore our stomp endpoints since they are protected using Stomp headers
                .ignoringRequestMatchers("/chat/**")
            )
            .headers(headers -> headers
                // allow same origin to frame our site to support iframe SockJS
                .frameOptions(frameOptions -> frameOptions
                    .sameOrigin()
                )
            )
            .authorizeHttpRequests(authorize -> authorize
                ...
            )
            ...
    }
}
@Configuration
@EnableWebSecurity
open class WebSecurityConfig {
    @Bean
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            csrf {
                ignoringRequestMatchers("/chat/**")
            }
            headers {
                frameOptions {
                    sameOrigin = true
                }
            }
            authorizeRequests {
                // ...
            }
            // ...
        }
    }
}

如果我们使用基于 XML 的配置,则可以使用“@6”。

If we use XML-based configuration, we can use thecsrf@request-matcher-ref.

<http ...>
    <csrf request-matcher-ref="csrfMatcher"/>

    <headers>
        <frame-options policy="SAMEORIGIN"/>
    </headers>

    ...
</http>

<b:bean id="csrfMatcher"
    class="AndRequestMatcher">
    <b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/>
    <b:constructor-arg>
        <b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
          <b:bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
            <b:constructor-arg value="/chat/**"/>
          </b:bean>
        </b:bean>
    </b:constructor-arg>
</b:bean>

Legacy WebSocket Configuration

在 Spring Security 5.8 之前,使用 Java 配置配置消息传递授权的方法是,扩展 AbstractSecurityWebSocketMessageBrokerConfigurer 并配置 MessageSecurityMetadataSourceRegistry。例如:

Before Spring Security 5.8, the way to configure messaging authorization using Java Configuration, was to extend the AbstractSecurityWebSocketMessageBrokerConfigurer and configure the MessageSecurityMetadataSourceRegistry. For example:

  • Java

  • Kotlin

@Configuration
public class WebSocketSecurityConfig
      extends AbstractSecurityWebSocketMessageBrokerConfigurer { // <1> 2

    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages
                .simpDestMatchers("/user/**").authenticated() (3)
    }
}
@Configuration
open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() { // <1> 2
    override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
        messages.simpDestMatchers("/user/**").authenticated() (3)
    }
}

这会确保:

This will ensure that:

1 Any inbound CONNECT message requires a valid CSRF token to enforce websocket-sameorigin
2 The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
3 Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in WebSocket Authorization

在你有自定义 SecurityExpressionHandler 且扩展了 AbstractSecurityExpressionHandler 并覆盖 createEvaluationContextInternalcreateSecurityExpressionRoot 的情况下,使用旧版配置很有帮助。为了延迟 Authorization 查找,新的 AuthorizationManager API 在评估表达式时不会调用这些内容。

Using the legacy configuration is helpful in the event that you have a custom SecurityExpressionHandler that extends AbstractSecurityExpressionHandler and overrides createEvaluationContextInternal or createSecurityExpressionRoot. In order to defer Authorization lookup, the new AuthorizationManager API does not invoke these when evaluating expressions.

如果你正在使用 XML,你可以简单地不使用 use-authorization-manager 元素或将其设置为 false,即可使用旧版 API。

If you are using XML, you can use the legacy APIs simply by not using the use-authorization-manager element or setting it to false.