1. 引言
在上一篇文章中,我們演示瞭如何將 WebSockets 添加到 Spring MVC 項目中。
在這裏,我們將描述如何 在 Spring MVC 中為 Spring WebSockets 添加安全功能。 在繼續之前,請確保您已經具備基本的 Spring MVC Security 覆蓋,如果沒有,請查看這篇文章。
2. Maven 依賴
以下是我們 WebSocket 實現中需要的主要兩個 Maven 依賴組:
首先,讓我們指定我們將使用的 Spring Framework 和 Spring Security 的總體版本:
<properties>
<spring.version>6.0.12</spring.version>
<spring-security.version>6.1.5</spring-security.version>
<spring-security-messaging.version>6.0.2</spring-security-messaging.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、spring-web、spring-webmvc、spring-security-web、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-messaging.version}</version>
</dependency>
您可以在 Maven Central 找到最新版本的 spring-websocket、spring-messaging 和 spring-security-messaging。
3. WebSocket 安全基礎
為了配置 WebSocket 安全,請包含 @EnableWebSocketSecurity 註解併發佈一個 AuthorizationManager<Message<?>> Bean。這可以通過使用 AuthorizationManagerMessageMatcherRegistry 來指定端點模式,例如:
@Configuration
@EnableWebSocketSecurity
public class SocketSecurityConfig {
@Bean
AuthorizationManager<Message<?>> messageAuthorizationManager(
MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages.simpDestMatchers("/secured/**", "/secured/**/**")
.authenticated()
.anyMessage()
.authenticated();
return messages.build();
}
}4. 安全套接字路由
現在我們已經瞭解了基本的套接字安全和類型匹配配置,可以結合套接字安全、視圖、STOMP(一種文本消息協議)、消息代理以及套接字控制器,從而在我們的 Spring MVC 應用程序中啓用安全的 WebSockets。
首先,讓我們為基本的 Spring 安全覆蓋設置我們的套接字視圖和控制器:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@EnableWebSecurity
@ComponentScan("com.baeldung.springsecuredsockets")
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
authorizationManagerRequestMatcherRegistry
.requestMatchers("/", "/index", "/authenticate").permitAll()
.requestMatchers("/secured/**/**", "/secured/**/**/**", "/secured/socket",
"/secured/success").authenticated()
.anyRequest().authenticated())
.formLogin(httpSecurityFormLoginConfigurer ->
httpSecurityFormLoginConfigurer.loginPage("/login").permitAll()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/authenticate")
.successHandler(loginSuccessHandler())
.failureUrl("/denied").permitAll())
//...
}
}第二,我們接下來設置實際的消息目的地,幷包含身份驗證要求:
@Configuration
@EnableWebSocketSecurity
public class SocketSecurityConfig {
@Bean
AuthorizationManager<Message<?>> messageAuthorizationManager(
MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages.simpDestMatchers("/secured/**", "/secured/**/**")
.authenticated()
.anyMessage()
.authenticated();
return messages.build();
}
}現在,在我們的<em 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. 同源策略
同源策略 要求所有與端點的交互必須來自交互發起的同一域名。
例如,如果您的 WebSocket 實現託管在 foo.com 上,並且您正在 強制執行同源策略。如果用户連接到託管在 foo.com 的客户端,然後在新瀏覽器中打開指向 bar.com 的連接,那麼 bar.com 將無法訪問您的 WebSocket 實現。
5.1. 覆蓋同源策略
Spring WebSockets 默認強制執行同源策略,而普通 WebSockets 則不具備此功能。
實際上,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();
}
}通過調用以 /csrf 結尾的端點,客户端可以獲取令牌並通過 CSRF 安全層進行身份驗證。
當使用 @EnableWebSocketSecurity 時,CSRF 的配置選項不可用,但預計將在未來的版本中添加。但是,如果您正在使用遺留的 AbstractSecurityWebSocketMessageBrokerConfigurer,則可以通過在 AbstractSecurityWebSocketMessageBrokerConfigurer 中添加以下配置來覆蓋 同一來源策略:
@Override
protected boolean sameOriginDisabled() {
return true;
}5.2. STOMP、SockJS 支持和 Frame 選項
使用 STOMP 和 SockJS 實現客户端對 Spring WebSockets 的支持很常見。
默認情況下,SockJS 配置為禁止通過 iframe 元素進行傳輸,以防止點擊劫持的威脅。
然而,在某些用例中,允許 iframe 元素利用 SockJS 傳輸是有益的。 要做到這一點,你可以創建一個 SecurityFilterChain Bean:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
//...
.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.sameOrigin()))
.authorizeHttpRequests(Customizer.withDefaults());
return http.build();
}請注意,在這個示例中,我們遵循了 同源策略,即使允許通過 iframe 進行傳輸。
6. OAuth2 支持
通過在標準 WebSecurityConfigurerAdapter 覆蓋之外,並對其進行擴展,來實現針對 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 的 WebSocket 和 WebSocket Security 參考文檔。