I’m working on a web application with an Angular 17 frontend and a Spring Boot backend. I’m trying to set up WebSocket communication on one page in order to receive real time updates from the backend.
Per the boss’ requirements, I’m using the RxJS WebSocket wrapper on the frontend. When I attempt to create the Web Socket connection, I get an error in the Chrome DevTools saying that WebSocket connection to 'wss://localhost:8443/ws' failed
. Followed by a Websocket connection closed
message.
The server logs seem to indicate that the websocket connection is established, but then immediately ended due to an EOFException: Transport error in StandardWebSocketSession[id=0c2b656b-5081-9312-9ec5-af58f0f6ff29, uri=wss://localhost:8443/ws] java.io.EOFException: null
.
The same setup works correctly when I run it in a small demo app, which leads me to believe there is something in the configuration or security settings of my Spring app that is causing this immediate closure/rejection of the websocket connection. What might be causing this error?
Here is my code.
Frontend:
@Injectable({
providedIn: 'root'
})
export class WebSocketService {
private socket$: WebSocketSubject<any> | null = null;
private latestMessage: Signal<any> = signal(null);
private authToken: string;
constructor(private authServerProvider: AuthServerProvider) {
this.authToken = this.authServerProvider.getToken();
const connectionString = PORTAL_SERVER_API_URL.replace('http', 'ws') + '/ws';
this.socket$ = this.socket$ = webSocket({
url: connectionString,
protocol: ['access_token', this.authToken],
openObserver: {
next: () => console.log('WebSocket connection opened')
},
closeObserver: {
next: (closeEvent) => console.log('WebSocket connection closed', closeEvent)
}
});
this.socket$.subscribe({
next: () => { console.log('Next'); },
error: (error) => { console.log('Error: ' + error); },
complete: () => { console.log('Complete'); },
});
console.log('Service constructed, socket created');
}
sendToServer(request: string) {
this.socket$.next({
request,
});
}
}
Backend:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler(), "/ws").setAllowedOrigins("*").addInterceptors(new HttpSessionHandshakeInterceptor());
}
@Bean
public WebSocketHandler webSocketHandler() {
return new WebSocketController();
}
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
container.setMaxSessionIdleTimeout(100000L);
return container;
}
}
public class WebSocketController extends TextWebSocketHandler {
private final ObjectMapper objectMapper = new ObjectMapper();
private static final Logger logger = LoggerFactory.getLogger(WebSocketController.class);
@Override
@PreAuthorize("permitAll()")
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
String payload = message.getPayload();
Map<String, String> incomingJson = objectMapper.readValue(payload, Map.class);
// Create a response JSON
Map<String, String> jsonResponse = new HashMap<>();
jsonResponse.put("response", incomingJson.get("request"));
// Convert to JSON string and send
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(jsonResponse)));
}
@Override
@PreAuthorize("permitAll()")
public void afterConnectionEstablished(WebSocketSession session) {
logger.debug("WebSocket connection established: " + session.getId());
logger.debug("Session attributes: " + session.getAttributes());
logger.debug("Session protocol: " + session.getAcceptedProtocol());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
logger.error("Transport error on session " + session.getId(), exception);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
logger.debug("WebSocket connection closed for session " + session.getId() + " with status: " + status);
}
}
And here is the complete console output:
**Chrome Devtools: **
web-socket.service.ts:33 - WebSocket connection to 'wss://localhost:8443/ws' failed:
WebSocketService @ web-socket.service.ts:33
WebSocketService_Factory @ web-socket.service.ts:46
ExecuteExtractDialogComponent_Factory @ execute-extract-dialog.component.ts:21
openRunDialog @ extracts.component.ts:102
ExtractsComponent_ng_container_25_Template_i_click_17_listener @ extracts.component.html:41
...
Error: [object Event]
WebSocket connection closed
CloseEvent {
isTrusted: true
bubbles: false
cancelBubble: false
cancelable: false
code: 1006
composed: false
currentTarget: WebSocket {__zone_symbol__openfalse: Array(1), __zone_symbol__errorfalse: Array(1), __zone_symbol__ON_PROPERTYopen: ƒ, __zone_symbol__ON_PROPERTYerror: ƒ, __zone_symbol__ON_PROPERTYclose: ƒ, …}
defaultPrevented: false
eventPhase: 0
reason: ""
returnValue: true
srcElement: WebSocket {__zone_symbol__openfalse: Array(1), __zone_symbol__errorfalse: Array(1), __zone_symbol__ON_PROPERTYopen: ƒ, __zone_symbol__ON_PROPERTYerror: ƒ, __zone_symbol__ON_PROPERTYclose: ƒ, …}
target: WebSocket {__zone_symbol__openfalse: Array(1), __zone_symbol__errorfalse: Array(1), __zone_symbol__ON_PROPERTYopen: ƒ, __zone_symbol__ON_PROPERTYerror: ƒ, __zone_symbol__ON_PROPERTYclose: ƒ, …}
timeStamp: 32752.699999999255
type: "close"
wasClean: false
[[Prototype]]: CloseEvent {...}
Spring Boot Server Logs:
2024-07-09 21:18:34,555 63893 [https-jsse-nio-8443-exec-6] TRACE o.s.s.w.a.i.RequestMatcherDelegatingAuthorizationManager - Checking authorization on SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@1c07e08e] using org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer$$Lambda$1740/0x00000008018fd8d0@63647e54
2024-07-09 21:18:34,555 63893 [https-jsse-nio-8443-exec-6] DEBUG o.s.security.web.FilterChainProxy - Secured GET /ws
2024-07-09 21:18:34,556 63894 [https-jsse-nio-8443-exec-6] TRACE o.s.w.s.s.s.WebSocketHandlerMapping - Mapped to HandlerExecutionChain with [org.springframework.web.socket.server.support.WebSocketHttpRequestHandler@41366d50] and 1 interceptors
2024-07-09 21:18:34,557 63895 [https-jsse-nio-8443-exec-6] DEBUG o.s.w.s.s.s.WebSocketHttpRequestHandler - GET /ws
2024-07-09 21:18:34,560 63898 [https-jsse-nio-8443-exec-6] TRACE o.s.w.s.s.s.DefaultHandshakeHandler - Processing request https://localhost:8443/ws with headers=[host:"localhost:8443", connection:"Upgrade", pragma:"no-cache", cache-control:"no-cache", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", upgrade:"websocket", origin:"https://localhost:4200", sec-websocket-version:"13", accept-encoding:"gzip, deflate, br, zstd", accept-language:"en-US,en;q=0.9", sec-websocket-key:"x2ViimlmVyvdISG3PaOAiQ==", sec-websocket-extensions:"permessage-deflate; client_max_window_bits", sec-websocket-protocol:"access_token, eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJraGF3a2luc0BxdWlja21lZGNsYWltcy5jb20iLCJhdXRoIjoiUk9MRV9TVVBFUl9BRE1JTiIsImV4cCI6MTcyMDY5NDMwMSwibWZhUmVxdWlyZWQiOmZhbHNlfQ.rXglaeJU-IPKBSjLn92eThls2HYx1IgGhC44JE9U8XMZOtgRFPJpvt6E6c_VGlTI48SBTe3CCCWGTRZw-2j5bw"]
2024-07-09 21:18:34,561 63899 [https-jsse-nio-8443-exec-6] TRACE o.s.w.s.s.s.DefaultHandshakeHandler - Upgrading to WebSocket, subProtocol=null, extensions=[]
2024-07-09 21:18:34,626 63964 [https-jsse-nio-8443-exec-6] DEBUG o.s.w.s.h.LoggingWebSocketHandlerDecorator - New StandardWebSocketSession[id=ebef1ca9-ff61-b045-47cc-5227f9c65f68, uri=wss://localhost:8443/ws]
2024-07-09 21:18:34,626 63964 [https-jsse-nio-8443-exec-6] DEBUG c.q.p.c.WebSocketController - WebSocket connection established: ebef1ca9-ff61-b045-47cc-5227f9c65f68
2024-07-09 21:18:34,626 63964 [https-jsse-nio-8443-exec-6] DEBUG c.q.p.c.WebSocketController - Session attributes: {}
2024-07-09 21:18:34,626 63964 [https-jsse-nio-8443-exec-6] DEBUG c.q.p.c.WebSocketController - Session protocol:
2024-07-09 21:18:34,631 63969 [https-jsse-nio-8443-exec-6] DEBUG o.s.w.s.h.LoggingWebSocketHandlerDecorator - Transport error in StandardWebSocketSession[id=ebef1ca9-ff61-b045-47cc-5227f9c65f68, uri=wss://localhost:8443/ws]
java.io.EOFException: null
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1297)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1247)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoint.java:1191)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:74)
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:183)
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:163)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:152)
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:57)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
2024-07-09 21:18:34,632 63970 [https-jsse-nio-8443-exec-6] ERROR c.q.p.c.WebSocketController - Transport error on session ebef1ca9-ff61-b045-47cc-5227f9c65f68
java.io.EOFException: null
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1297)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1247)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoint.java:1191)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:74)
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:183)
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:163)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:152)
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:57)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
2024-07-09 21:18:34,637 63975 [https-jsse-nio-8443-exec-6] DEBUG o.s.w.s.h.LoggingWebSocketHandlerDecorator - StandardWebSocketSession[id=ebef1ca9-ff61-b045-47cc-5227f9c65f68, uri=wss://localhost:8443/ws] closed with CloseStatus[code=1006, reason=null]
2024-07-09 21:18:34,638 63976 [https-jsse-nio-8443-exec-6] DEBUG c.q.p.c.WebSocketController - WebSocket connection closed for session ebef1ca9-ff61-b045-47cc-5227f9c65f68 with status: CloseStatus[code=1006, reason=null]
I’ve tried changing various security configuration settings in the main config class to no avail. This is the current state of the SecurityFilterChain:
@Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
String[] permitAllPaths = jwtProperties.getPermitAllPaths().toArray(new String[0]);
var auth1 = AuthorityAuthorizationManager.<RequestAuthorizationContext>hasRole("USER");
auth1.setRoleHierarchy(roleHierarchy());
return http.csrf().disable().
headers().frameOptions().disable().and()
.cors().configurationSource(request -> getCorsConfiguration()).and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterAfter(tokenAuthenticationFilter, SecurityContextHolderFilter.class)
.authorizeHttpRequests().requestMatchers(permitAllPaths).permitAll()
.requestMatchers("/api/**").authenticated().requestMatchers("/ws/**").permitAll()
.and()
.securityContext((securityContext) -> securityContext
.securityContextRepository(new RequestAttributeSecurityContextRepository())
).authenticationProvider(filteringAuthenticationProvider())
.build();
}
khh is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.