对于最新的稳定版本,请使用 Spring Security 7.0.4spring-doc.cadn.net.cn

WebSocket 安全

Spring Security 4 增加了对保护 Spring 的 WebSocket 支持 的功能。 本节将介绍如何使用 Spring Security 的 WebSocket 支持。spring-doc.cadn.net.cn

直接支持 JSR-356

Spring Security 不提供对 JSR-356 的直接支持,因为这样做几乎没有什么价值。 这是因为消息格式是未知的,而Spring 几乎无法对未知格式进行安全保护。 此外,JSR-356 本身并未提供拦截消息的机制,因此若要实现安全性将具有侵入性。spring-doc.cadn.net.cn

WebSocket 身份验证

WebSocket 在建立连接时会复用 HTTP 请求中的认证信息。 这意味着 Principal 中的 HttpServletRequest 将被传递给 WebSocket。 如果你使用的是 Spring Security,Principal 上的 HttpServletRequest 会被自动覆盖。spring-doc.cadn.net.cn

更具体地说,要确保用户已通过 WebSocket 应用程序的身份验证,只需配置 Spring Security 对基于 HTTP 的 Web 应用程序进行身份验证即可。spring-doc.cadn.net.cn

WebSocket 授权

Spring Security 4.0 通过 Spring Messaging 抽象层引入了对 WebSocket 的授权支持。spring-doc.cadn.net.cn

在 Spring Security 5.8 中,此支持已更新为使用 AuthorizationManager API。spring-doc.cadn.net.cn

要使用 Java 配置来设置授权,只需添加 @EnableWebSocketSecurity 注解,并发布一个 AuthorizationManager<Message<?>> Bean;或者在XML 中使用 use-authorization-manager 属性。 实现此目的的一种方式是使用 AuthorizationManagerMessageMatcherRegistry 来指定端点模式,如下所示:spring-doc.cadn.net.cn

@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 任何入站的 CONNECT 消息都需要一个有效的 CSRF Tokens,以强制实施同源策略
2 对于任何入站请求,SecurityContextHolder 都会使用 simpUser 头部属性中的用户信息进行填充。
3 我们的消息需要适当的授权。具体来说,任何以 /user/ 开头的入站消息都需要 ROLE_USER 权限。您可以在WebSocket 授权中找到有关授权的更多详细信息。

自定义授权

使用 AuthorizationManager 时,自定义非常简单。 例如,您可以发布一个 AuthorizationManager,通过 AuthorityAuthorizationManager 要求所有消息都具有 "USER" 角色,如下所示:spring-doc.cadn.net.cn

@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"/>

有多种方式可以进一步匹配消息,如下方更高级的示例所示:spring-doc.cadn.net.cn

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

这将确保:spring-doc.cadn.net.cn

1 任何没有目标地址的消息(即除 MESSAGE 或 SUBSCRIBE 类型之外的任何消息)都要求用户必须经过身份验证。
2 任何人都可以订阅 /user/queue/errors
3 任何目标地址以“/app/”开头的消息都将要求用户拥有 ROLE_USER 角色。
4 任何以 "/user/" 或 "/topic/friends/" 开头且类型为 SUBSCRIBE 的消息都需要 ROLE_USER 权限。
5 任何其他类型为 MESSAGE 或 SUBSCRIBE 的消息都会被拒绝。由于第6点,我们实际上不需要此步骤,但它展示了如何匹配特定的消息类型。
6 任何其他消息都会被拒绝。这样做是个好主意,可以确保你不会遗漏任何消息。

迁移 SpEL 表达式

如果您从较早版本的Spring Security迁移过来,您的目标匹配器可能包含SpEL表达式。 建议将这些内容改为使用具体的AuthorizationManager实现,因为这样可以独立进行测试。spring-doc.cadn.net.cn

然而,为了简化迁移,你也可以使用如下类:<br>spring-doc.cadn.net.cn

public final class MessageExpressionAuthorizationManager implements AuthorizationManager<MessageAuthorizationContext<?>> {

	private SecurityExpressionHandler<Message<?>> expressionHandler = new DefaultMessageSecurityExpressionHandler();

	private Expression expression;

	public MessageExpressionAuthorizationManager(String expressionString) {
		Assert.hasText(expressionString, "expressionString cannot be empty");
		this.expression = this.expressionHandler.getExpressionParser().parseExpression(expressionString);
	}

	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MessageAuthorizationContext<?> context) {
		EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, context.getMessage());
		boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
		return new ExpressionAuthorizationDecision(granted, this.expression);
	}

}

并为每个无法迁移的匹配器指定一个实例:spring-doc.cadn.net.cn

@Configuration
public class WebSocketSecurityConfig {

    @Bean
    public AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
        messages
                // ...
                .simpSubscribeDestMatchers("/topic/friends/{friend}").access(new MessageExpressionAuthorizationManager("#friends == 'john"));
                // ...

        return messages.build();
    }
}
@Configuration
open class WebSocketSecurityConfig {
    fun messageAuthorizationManager(messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?> {
        messages
            // ..
            .simpSubscribeDestMatchers("/topic/friends/{friends}").access(MessageExpressionAuthorizationManager("#friends == 'john"))
            // ...

        return messages.build()
    }
}

WebSocket 授权说明

要正确地保护您的应用程序,您需要了解Spring的WebSocket支持。spring-doc.cadn.net.cn

基于消息类型的 WebSocket 授权

您需要理解 SUBSCRIBEMESSAGE 消息类型之间的区别以及它们在 Spring 中的工作方式。spring-doc.cadn.net.cn

考虑一个聊天应用:spring-doc.cadn.net.cn

  • The system can send a notification MESSAGE to all users through a destination of /topic/system/notifications.spring-doc.cadn.net.cn

  • 客户端可以通过订阅 SUBSCRIBE/topic/system/notifications 来接收通知。spring-doc.cadn.net.cn

我们希望客户端能够订阅/SUBSCRIBE,但并不希望他们能够向该目的地发送//topic/system/notifications。 如果允许向/MESSAGE 发送/MESSAGE,客户端可以直接向该端点发送消息并冒充系统。spring-doc.cadn.net.cn

一般而言,应用程序会拒绝任何发送到以/topic//queue/ 开头的目的地的 MESSAGEspring-doc.cadn.net.cn

WebSocket 授权目标

您也应该了解目的地是如何进行转换的。spring-doc.cadn.net.cn

考虑一个聊天应用:spring-doc.cadn.net.cn

  • 用户可以通过向 /app/chat 目的地发送消息来给特定用户发送消息。spring-doc.cadn.net.cn

  • 该应用会接收到消息,并确保from属性被指定为当前用户(我们不能信任客户端)。spring-doc.cadn.net.cn

  • 然后,应用程序通过使用SimpMessageSendingOperations.convertAndSendToUser("toUser", "/queue/messages", message)将消息发送给接收方。spring-doc.cadn.net.cn

  • The message gets turned into the destination of /queue/user/messages-<sessionid>.spring-doc.cadn.net.cn

通过这个聊天应用程序,我们希望让客户监听/user/queue,这会被转换为/queue/user/messages-<sessionid>。 但是,我们不希望客户端能够监听/queue/*,因为这样会使客户端看到所有用户的消息。spring-doc.cadn.net.cn

通常,应用程序会拒绝任何发送给以 (/topic//queue/) 开头的消息的 SUBSCRIBE 请求。 我们可能会提供例外情况来处理类似的情况spring-doc.cadn.net.cn

出站消息

The Spring Framework 参考文档包含一个名为 “消息流” 的部分,该部分描述了消息如何在系统中流动。 注意,Spring Security 仅保护 clientInboundChannel。 Spring Security 不尝试保护 clientOutboundChannelspring-doc.cadn.net.cn

这是主要原因之一,即性能。 对于每个传入的消息,通常会发送出很多消息。 与其对传出的消息进行安全保护,我们建议对端点的订阅进行安全保护。spring-doc.cadn.net.cn

实施同源策略

请注意,浏览器不会强制执行WebSocket连接的同源策略。 这是一项极其重要的考虑因素。spring-doc.cadn.net.cn

为什么需要同源?

考虑以下场景。 用户访问bank.com并登录其账户。 该用户在同一浏览器中打开另一个标签页,然后访问evil.com。 相同源策略确保evil.com无法从bank.com读取数据或写入数据。spring-doc.cadn.net.cn

使用 WebSockets 时,同源策略并不适用。 实际上,除非 `bank.com` 明示禁止,`evil.com` 可以代表用户读取和写入数据。 这意味着用户通过 WebSocket 可以进行的任何操作(例如转账),`evil.com` 都可以替用户执行。spring-doc.cadn.net.cn

由于SockJS试图模拟WebSocket,因此它也会绕过同源策略。 这意味着当开发者使用SockJS时,需要明确地保护其应用程序免受外部域的攻击。spring-doc.cadn.net.cn

Spring WebSocket 允许的源

庆幸的是,自 Spring 4.1.5 版本起,Spring 的 WebSocket 和 SockJS 支持限制了对当前域的访问。 Spring Security 添加了一层额外的保护以提供 纵深防御spring-doc.cadn.net.cn

将 CSRF 添加到 Stomp 头

默认情况下,Spring Security 要求在任何 xref page 消息类型中包含CSRFTokens。 这确保了只有能够访问 CSRF Tokens的站点才能连接。 由于只有相同的源可以访问 CSRF Tokens,外部域名不允许进行连接。spring-doc.cadn.net.cn

通常我们需要在HTTP头或HTTP参数中包含CSRFTokens。 然而,SockJS并不允许这些选项。 因此,我们必须将Tokens包含在Stomp头部中。spring-doc.cadn.net.cn

应用程序可以通过访问名为 xref page 的请求属性来 获取 CSRFTokens。 例如,以下内容允许在JSP中访问 CsrfToken:spring-doc.cadn.net.cn

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

如果使用静态HTML,您可以在REST端点处暴露CsrfToken。 例如,以下内容会在CsrfToken URL 上暴露/csrf:spring-doc.cadn.net.cn

@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 和Tokens。spring-doc.cadn.net.cn

我们现在可以在我们的Stomp客户端中包含Tokens:spring-doc.cadn.net.cn

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

})

在 WebSockets 中禁用 CSRF

在使用@EnableWebSocketSecurity时,CSRF 现在是不可配置的,尽管这在未来版本中可能会被添加。

要禁用CSRF,而不是使用@EnableWebSocketSecurity,您可以使用XML支持或手动添加Spring Security组件,如下所示:spring-doc.cadn.net.cn

@Configuration
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {

    private final ApplicationContext applicationContext;

    private final AuthorizationManager<Message<?>> authorizationManager;

    public WebSocketSecurityConfig(ApplicationContext applicationContext, AuthorizationManager<Message<?>> authorizationManager) {
        this.applicationContext = applicationContext;
        this.authorizationManager = authorizationManager;
    }

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

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        AuthorizationChannelInterceptor authz = new AuthorizationChannelInterceptor(authorizationManager);
        AuthorizationEventPublisher publisher = new SpringAuthorizationEventPublisher(applicationContext);
        authz.setAuthorizationEventPublisher(publisher);
        registration.interceptors(new SecurityContextChannelInterceptor(), authz);
    }
}
@Configuration
open class WebSocketSecurityConfig(val applicationContext: ApplicationContext, val authorizationManager: AuthorizationManager<Message<*>>) : WebSocketMessageBrokerConfigurer {
    @Override
    override fun addArgumentResolvers(argumentResolvers: List<HandlerMethodArgumentResolver>) {
        argumentResolvers.add(AuthenticationPrincipalArgumentResolver())
    }

    @Override
    override fun configureClientInboundChannel(registration: ChannelRegistration) {
        var authz: AuthorizationChannelInterceptor = AuthorizationChannelInterceptor(authorizationManager)
        var publisher: AuthorizationEventPublisher = SpringAuthorizationEventPublisher(applicationContext)
        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>

另一方面,如果您正在使用 遗留的 AbstractSecurityWebSocketMessageBrokerConfigurer 并希望允许其他域访问您的站点,您可以禁用 Spring Security 的保护。 例如,在 Java 配置中,您可以使用以下内容:spring-doc.cadn.net.cn

@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    ...

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

    // ...

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

自定义表达式处理器

有时,您可能需要自定义处理access XML元素中定义的intercept-message表达式的方式。 为此,可以创建一个类型为SecurityExpressionHandler<MessageAuthorizationContext<?>>的类,并在XML定义中引用它,如下所示:spring-doc.cadn.net.cn

<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进行包装,如下所示:spring-doc.cadn.net.cn

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

使用 SockJS

SockJS 提供了回退传输方式,以支持较旧的浏览器。 当使用回退选项时,我们需要放宽一些安全限制,以便SockJS能够与Spring Security一起工作。spring-doc.cadn.net.cn

SockJS 与 frame-options

SockJS 可能会使用一个 利用 iframe 的传输方式。 默认情况下,Spring Security 拒绝站点被嵌入框架以防止点击劫持攻击。 为了使 SockJS 基于框架的传输能够正常工作,我们需要配置 Spring Security 以便允许相同源的框架嵌入内容。spring-doc.cadn.net.cn

您可以使用X-Frame-Options元素自定义xref pagespring-doc.cadn.net.cn

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

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

同样,您可以通过使用以下方式来自定义框架选项以在Java配置中使用相同的源:spring-doc.cadn.net.cn

@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 与放松 CSRF

SockJS 使用 CONNECT 消息中的 POST 请求来进行任何基于 HTTP 的传输。 通常,我们需要在 HTTP 头部或 HTTP 参数中包含 CSRF Tokens。 然而,SockJS 并不支持这些选项。 相反,我们必须将Tokens包含在 Stomp 头部中,如 《添加 CSRF 到 Stomp 头部》 中所述。spring-doc.cadn.net.cn

这也意味着我们需要在Web层放松CSRF保护。 具体来说,我们希望为连接URL禁用CSRF保护。 我们并不想对每个URL都禁用CSRF保护。 否则,我们的网站将容易遭受CSRF攻击。spring-doc.cadn.net.cn

我们可以通过提供一个CSRF RequestMatcher 来轻松实现这一点。我们的Java配置使这变得很容易。 例如,如果我们的stomp端点是/chat,我们可以通过以下配置仅对以/chat/开头的URL禁用CSRF保护:spring-doc.cadn.net.cn

@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的配置,我们可以使用csrf@request-matcher-refspring-doc.cadn.net.cn

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

遗留的 WebSocket 配置

在 Spring Security 5.8 之前,使用 Java 配置来配置消息授权的方式是扩展 AbstractSecurityWebSocketMessageBrokerConfigurer 并配置 MessageSecurityMetadataSourceRegistry。 例如:spring-doc.cadn.net.cn

@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)
    }
}

这将确保:spring-doc.cadn.net.cn

1 任何入站的 CONNECT 消息需要一个有效的 CSRF Tokens来强制执行 同源策略
2 The SecurityContextHolder 在任何入站请求中都会在 simpUser 头属性内填充用户信息。
3 我们的消息需要正确的授权。具体来说,任何以"/user/"开头的入站消息都需要 ROLE_USER 权限。更多关于授权的详细信息可以在 WebSocket 授权 中找到。

使用遗留配置在您有一个自定义SecurityExpressionHandler类继承了AbstractSecurityExpressionHandler并重写了createEvaluationContextInternalcreateSecurityExpressionRoot的情况下非常有用。 为了推迟Authorization查找,新的AuthorizationManagerAPI在评估表达式时不会调用这些方法。spring-doc.cadn.net.cn

如果您使用XML配置,只需不使用use-authorization-manager元素或将其设置为false,就可以继续使用遗留API。spring-doc.cadn.net.cn