Spring 具有 spring 安全性的 websocket 身份验证:401 错误
Spring websocket authentication with spring security: 401 error
我想用 spring security
验证 WebSocket。从 spring official document 23.2 WebSocket Authentication
,WebSocket 将重用在建立 WebSocket 连接时在 HTTP 请求中找到的相同身份验证信息。所以我设置 spring 安全性来验证 rest service
。如果用户通过了rest认证,则有WebSocket连接的权限,否则无法建立WebSocket连接。以下是代码:
登录休息服务:WssAuthService.java
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* Authentication service.
*/
@RestController
@RequestMapping(path = "/dm")
public class WssAuthService {
@RequestMapping(path = "/login", method = RequestMethod.GET)
public String login(){
return "Login success to WssBroker...";
}
}
spring 安全配置:WebSecurityConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public final static String REALM="MY_TEST_REALM";
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN")
.and().withUser("test").password("test").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don't need sessions to be created.
}
@Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
return new CustomBasicAuthenticationEntryPoint();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
// altough this seems like useless code,
// its required to prevend spring boot auto-configuration
return super.authenticationManagerBean();
}
}
websocket 服务器配置:WssBrokerConfig.java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
@EnableWebSocketMessageBroker
public class WssBrokerConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().authenticated()
.simpSubscribeDestMatchers("/topic/notification").permitAll()
.simpDestMatchers("/**").authenticated()
// .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER")
// .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll()
.anyMessage().denyAll();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/ws");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
/**Note: setAllowedOrigins is important here: since we have both http & websocket servers, cross-origin accesses should be enabled */
registry.addEndpoint("/dm-ws").setAllowedOrigins("*").withSockJS();
}
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter(mapper);
return converter;
}
}
websocket client:WebSocketClient.java
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompFrameHandler;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.messaging.simp.stomp.StompSessionHandler;
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.messaging.WebSocketStompClient;
import org.springframework.web.socket.sockjs.client.SockJsClient;
import org.springframework.web.socket.sockjs.client.Transport;
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
//@JsonIgnoreProperties(ignoreUnknown = true)
public class WebsocketClient {
public static void main(String[] args){
// 1.login to rest service
authToRest();
// 2.establish websocket connection
openConnection();
}
private static HttpHeaders getHeaders(){
String plainCredentials="admin:admin";
String base64Credentials = Base64.getEncoder().encodeToString(plainCredentials.getBytes());
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Credentials);
return headers;
}
public static void authToRest(){
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> request = new HttpEntity<String>(getHeaders());
ResponseEntity<String> response = restTemplate.exchange("http://localhost:8082/dm/login", HttpMethod.GET, request, String.class);
System.out.println(response.getBody());
}
public static void openConnection(){
List<Transport> transports = new ArrayList<>(1);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
WebSocketClient transport = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(transport);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.afterPropertiesSet();
stompClient.setTaskScheduler(taskScheduler); // for heartbeats
StompSessionHandler myHandler = new MyStompHandler();
String url = "ws://localhost:8082/dm-ws";
stompClient.connect(url, myHandler);
//block the thread
CountDownLatch latch = new CountDownLatch(1);
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class MyStompHandler extends StompSessionHandlerAdapter {
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/topic/response", new StompFrameHandler() {
@Override
public Type getPayloadType(StompHeaders headers) {
return Object.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
System.out.println(payload);
}
});
}
@Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload,
Throwable exception) {
System.out.println(exception.getMessage());
}
@Override
public void handleTransportError(StompSession session, Throwable exception) {
exception.printStackTrace();
System.out.println("transport error.");
}
}
}
但结果是我可以得到剩余的响应,但无法与WebSocket服务器建立连接。这是错误信息:
14:31:35.368 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:8082/dm/login" resulted in 200 (null)
14:31:35.369 [main] DEBUG org.springframework.web.client.RestTemplate - Reading [java.lang.String] as "text/plain;charset=UTF-8" using [org.springframework.http.converter.StringHttpMessageConverter@4b553d26]
Login success to WssBroker...
14:31:35.415 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler - Initializing ExecutorService
14:31:35.569 [main] DEBUG org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport - Executing SockJS Info request, url=http://localhost:8082/dm-ws/info
14:31:35.569 [main] DEBUG org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:8082/dm-ws/info"
14:31:35.574 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:8082/dm-ws/info" resulted in 401 (null); invoking error handler
14:31:35.578 [main] ERROR org.springframework.web.socket.sockjs.client.SockJsClient - Initial SockJS "Info" request to server failed, url=ws://localhost:8082/dm-ws
org.springframework.web.client.HttpClientErrorException: 401 null
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:595)
at org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport.executeInfoRequestInternal(RestTemplateXhrTransport.java:138)
at org.springframework.web.socket.sockjs.client.AbstractXhrTransport.executeInfoRequest(AbstractXhrTransport.java:155)
at org.springframework.web.socket.sockjs.client.SockJsClient.getServerInfo(SockJsClient.java:286)
at org.springframework.web.socket.sockjs.client.SockJsClient.doHandshake(SockJsClient.java:254)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:274)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:255)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:235)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:219)
at com.hspi.dm.console.message.WebsocketClient.openConnection(WebsocketClient.java:66)
at com.hspi.dm.console.message.WebsocketClient.main(WebsocketClient.java:35)
14:31:35.583 [main] DEBUG org.springframework.messaging.simp.stomp.DefaultStompSession - Failed to connect session id=d8fda5d4-ba5a-7d22-f517-74e939096bfa
org.springframework.web.client.HttpClientErrorException: 401 null
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:595)
at org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport.executeInfoRequestInternal(RestTemplateXhrTransport.java:138)
at org.springframework.web.socket.sockjs.client.AbstractXhrTransport.executeInfoRequest(AbstractXhrTransport.java:155)
at org.springframework.web.socket.sockjs.client.SockJsClient.getServerInfo(SockJsClient.java:286)
at org.springframework.web.socket.sockjs.client.SockJsClient.doHandshake(SockJsClient.java:254)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:274)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:255)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:235)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:219)
at com.hspi.dm.console.message.WebsocketClient.openConnection(WebsocketClient.java:66)
at com.hspi.dm.console.message.WebsocketClient.main(WebsocketClient.java:35)
有人可以帮助我吗?谢谢。
更新
来自 this post,问题似乎是我没有保护 websocket endpoint
。我尝试了这两种解决方案,但没有用,也许我的方法不对。希望正确的方法来保护 websocket endponit
.
嗯,我误会了 WebSockets reuse the same authentication information that is found in the HTTP request when the WebSocket connection was made.
和 spring documentation。
我应该对升级到 WebSocket 而不是 rest 服务的 HTTP 请求进行身份验证。同时,需要更改 WebSocket 配置。
1.在 WebSocket 中禁用 CSRF
将 sameOriginDisabled()
添加到 WssBrokerConfig
。
@Configuration
public class WssBrokerConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
...
@Override
protected boolean sameOriginDisabled() {
return true;
}
}
2.连接 WebSocketHttpHeaders
构造WebSocketHttpHeaders
并在connect()
之前将用户凭证添加到headers。用户名和密码需要用base64加密。
String plainCredentials="admin:admin";
String base64Credentials = Base64.getEncoder().encodeToString(plainCredentials.getBytes());
final WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.add("Authorization", "Basic " + base64Credentials);
stompClient.connect(url, headers, myHandler);
我想用 spring security
验证 WebSocket。从 spring official document 23.2 WebSocket Authentication
,WebSocket 将重用在建立 WebSocket 连接时在 HTTP 请求中找到的相同身份验证信息。所以我设置 spring 安全性来验证 rest service
。如果用户通过了rest认证,则有WebSocket连接的权限,否则无法建立WebSocket连接。以下是代码:
登录休息服务:WssAuthService.java
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* Authentication service.
*/
@RestController
@RequestMapping(path = "/dm")
public class WssAuthService {
@RequestMapping(path = "/login", method = RequestMethod.GET)
public String login(){
return "Login success to WssBroker...";
}
}
spring 安全配置:WebSecurityConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public final static String REALM="MY_TEST_REALM";
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN")
.and().withUser("test").password("test").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don't need sessions to be created.
}
@Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
return new CustomBasicAuthenticationEntryPoint();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
// altough this seems like useless code,
// its required to prevend spring boot auto-configuration
return super.authenticationManagerBean();
}
}
websocket 服务器配置:WssBrokerConfig.java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
@EnableWebSocketMessageBroker
public class WssBrokerConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().authenticated()
.simpSubscribeDestMatchers("/topic/notification").permitAll()
.simpDestMatchers("/**").authenticated()
// .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER")
// .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll()
.anyMessage().denyAll();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/ws");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
/**Note: setAllowedOrigins is important here: since we have both http & websocket servers, cross-origin accesses should be enabled */
registry.addEndpoint("/dm-ws").setAllowedOrigins("*").withSockJS();
}
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter(mapper);
return converter;
}
}
websocket client:WebSocketClient.java
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompFrameHandler;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.messaging.simp.stomp.StompSessionHandler;
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.messaging.WebSocketStompClient;
import org.springframework.web.socket.sockjs.client.SockJsClient;
import org.springframework.web.socket.sockjs.client.Transport;
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
//@JsonIgnoreProperties(ignoreUnknown = true)
public class WebsocketClient {
public static void main(String[] args){
// 1.login to rest service
authToRest();
// 2.establish websocket connection
openConnection();
}
private static HttpHeaders getHeaders(){
String plainCredentials="admin:admin";
String base64Credentials = Base64.getEncoder().encodeToString(plainCredentials.getBytes());
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Credentials);
return headers;
}
public static void authToRest(){
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> request = new HttpEntity<String>(getHeaders());
ResponseEntity<String> response = restTemplate.exchange("http://localhost:8082/dm/login", HttpMethod.GET, request, String.class);
System.out.println(response.getBody());
}
public static void openConnection(){
List<Transport> transports = new ArrayList<>(1);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
WebSocketClient transport = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(transport);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.afterPropertiesSet();
stompClient.setTaskScheduler(taskScheduler); // for heartbeats
StompSessionHandler myHandler = new MyStompHandler();
String url = "ws://localhost:8082/dm-ws";
stompClient.connect(url, myHandler);
//block the thread
CountDownLatch latch = new CountDownLatch(1);
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class MyStompHandler extends StompSessionHandlerAdapter {
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/topic/response", new StompFrameHandler() {
@Override
public Type getPayloadType(StompHeaders headers) {
return Object.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
System.out.println(payload);
}
});
}
@Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload,
Throwable exception) {
System.out.println(exception.getMessage());
}
@Override
public void handleTransportError(StompSession session, Throwable exception) {
exception.printStackTrace();
System.out.println("transport error.");
}
}
}
但结果是我可以得到剩余的响应,但无法与WebSocket服务器建立连接。这是错误信息:
14:31:35.368 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:8082/dm/login" resulted in 200 (null)
14:31:35.369 [main] DEBUG org.springframework.web.client.RestTemplate - Reading [java.lang.String] as "text/plain;charset=UTF-8" using [org.springframework.http.converter.StringHttpMessageConverter@4b553d26]
Login success to WssBroker...
14:31:35.415 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler - Initializing ExecutorService
14:31:35.569 [main] DEBUG org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport - Executing SockJS Info request, url=http://localhost:8082/dm-ws/info
14:31:35.569 [main] DEBUG org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:8082/dm-ws/info"
14:31:35.574 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:8082/dm-ws/info" resulted in 401 (null); invoking error handler
14:31:35.578 [main] ERROR org.springframework.web.socket.sockjs.client.SockJsClient - Initial SockJS "Info" request to server failed, url=ws://localhost:8082/dm-ws
org.springframework.web.client.HttpClientErrorException: 401 null
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:595)
at org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport.executeInfoRequestInternal(RestTemplateXhrTransport.java:138)
at org.springframework.web.socket.sockjs.client.AbstractXhrTransport.executeInfoRequest(AbstractXhrTransport.java:155)
at org.springframework.web.socket.sockjs.client.SockJsClient.getServerInfo(SockJsClient.java:286)
at org.springframework.web.socket.sockjs.client.SockJsClient.doHandshake(SockJsClient.java:254)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:274)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:255)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:235)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:219)
at com.hspi.dm.console.message.WebsocketClient.openConnection(WebsocketClient.java:66)
at com.hspi.dm.console.message.WebsocketClient.main(WebsocketClient.java:35)
14:31:35.583 [main] DEBUG org.springframework.messaging.simp.stomp.DefaultStompSession - Failed to connect session id=d8fda5d4-ba5a-7d22-f517-74e939096bfa
org.springframework.web.client.HttpClientErrorException: 401 null
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:595)
at org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport.executeInfoRequestInternal(RestTemplateXhrTransport.java:138)
at org.springframework.web.socket.sockjs.client.AbstractXhrTransport.executeInfoRequest(AbstractXhrTransport.java:155)
at org.springframework.web.socket.sockjs.client.SockJsClient.getServerInfo(SockJsClient.java:286)
at org.springframework.web.socket.sockjs.client.SockJsClient.doHandshake(SockJsClient.java:254)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:274)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:255)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:235)
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:219)
at com.hspi.dm.console.message.WebsocketClient.openConnection(WebsocketClient.java:66)
at com.hspi.dm.console.message.WebsocketClient.main(WebsocketClient.java:35)
有人可以帮助我吗?谢谢。
更新
来自 this post,问题似乎是我没有保护 websocket endpoint
。我尝试了这两种解决方案,但没有用,也许我的方法不对。希望正确的方法来保护 websocket endponit
.
嗯,我误会了 WebSockets reuse the same authentication information that is found in the HTTP request when the WebSocket connection was made.
和 spring documentation。
我应该对升级到 WebSocket 而不是 rest 服务的 HTTP 请求进行身份验证。同时,需要更改 WebSocket 配置。
1.在 WebSocket 中禁用 CSRF
将 sameOriginDisabled()
添加到 WssBrokerConfig
。
@Configuration
public class WssBrokerConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
...
@Override
protected boolean sameOriginDisabled() {
return true;
}
}
2.连接 WebSocketHttpHeaders
构造WebSocketHttpHeaders
并在connect()
之前将用户凭证添加到headers。用户名和密码需要用base64加密。
String plainCredentials="admin:admin";
String base64Credentials = Base64.getEncoder().encodeToString(plainCredentials.getBytes());
final WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.add("Authorization", "Basic " + base64Credentials);
stompClient.connect(url, headers, myHandler);