-
Notifications
You must be signed in to change notification settings - Fork 40.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
In 3.0.x and later, Spring Security cannot be used to secure a WebSocket upgrade request when using Jetty #37115
Comments
Thanks for the sample. The difference in behaviour is due to different ordering of the filters that handle the initial upgrade request. With Tomcat, this filter is at the end of the chain. Crucially, this means that Spring Security's filter runs before the upgrade request is handled and can require basic authentication. With Jetty, this filter is at the start of the chain and its handling of the upgrade request is such that the rest of the chain isn't called. Crucially, this means that Spring Security's filter doesn't get a chance to run. With both Tomcat and Jetty, the filter that handles upgrades is registered last so it should be at the end of the chain. However, that's not the case with Jetty because Jetty forces it to the start of the chain. Unfortunately, this clashes with a filter-based security framework, such as Spring Security, preventing it from securing upgrade requests. This appears to have been a change in Jetty 10. We could possibly override this behavior by registering Jetty's For these reasons I think it would be better to address this in Jetty itself. To that end, please open a Jetty issue for this so that they can investigate. Looking at jetty/jetty.project#5648 (which drove the change to the filter's order) they did consider filters like Spring Security but they talk about If a general fix in Jetty isn't possible, we can re-open the issue and consider a solution that's specific to Spring Boot. |
The reason for the websocket upgrade filter being first is simply because there are thousands of existing Filters out in the wild that modify the request / response, wrap the HttpServletRequest and/or HttpServletResponse, or provide overrides of the HttpServletRequest.getInputStream() or HttpServletResponse.getOutputStream() that all break when websocket is in the mix. The decision to put upgrade at the start is intentional to not break the thousands of webapps that are not websocket aware. Also, (putting on my jakarta websocket hat), there is zero requirement for a server that supports jakarta websocket to support that upgrade via a filter. Some actual implementations of
Assuming that the (putting my jetty hat back on) Jetty at one point had the A workaround for Spring Boot is to add the existing |
@wilkinsona would you like PR that introduces a special I suggest a import jakarta.websocket.Endpoint;
import jakarta.websocket.server.ServerApplicationConfig;
import jakarta.websocket.server.ServerContainer;
import jakarta.websocket.server.ServerEndpoint;
import jakarta.websocket.server.ServerEndpointConfig;
@HandlesTypes({ServerApplicationConfig.class, ServerEndpoint.class, Endpoint.class})
public class SpringBootJettyWebSocketUpgradeInitializer implements ServletContainerInitializer |
Thanks, @joakime. Sounds like we need to try to do something in Boot to address this.
Thanks for the offer. Boot's doesn't honour the @rcosne, you can work around your problem with the following addition to your app: @Bean
FilterRegistrationBean<WebSocketUpgradeFilter> webSocketUpgradeFilter() {
FilterRegistrationBean<WebSocketUpgradeFilter> registration = new FilterRegistrationBean<>(new WebSocketUpgradeFilter());
registration.setAsyncSupported(true);
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setName(WebSocketUpgradeFilter.class.getName());
registration.setOrder(Ordered.LOWEST_PRECEDENCE);
registration.setUrlPatterns(List.of("/*"));
return registration;
} We can probably add this bean directly to Boot but, given what @joakime has said above, I am a little wary of the potential for a regression. If someone's using Jetty and WebSockets without Spring Security they may be relying on the current filter ordering. |
As suspected, Spring Boot 3.0.x (I tested 3.0.10) is also affected and 2.7.x (I tested 2.7.15) is not affected. |
Hi @wilkinsona, the workaround works ! Thank you. |
@wilkinsona nice. BTW, If you have a user of Jetty 9, there's an extra requirement for that In Jetty 9, there is also a ServletContext attribute requirement for it to work. (thankfully we got rid of this requirement in Jetty 10) @Bean
FilterRegistrationBean<WebSocketUpgradeFilter> webSocketUpgradeFilter() {
WebSocketUpgradeFilter websocketFilter = new WebSocketUpgradeFilter();
getServletContext().setAttribute(WebSocketUpgradeFilter.ATTR_KEY, websocketFilter);
FilterRegistrationBean<WebSocketUpgradeFilter> registration = new FilterRegistrationBean<>(websocketFilter);
registration.setAsyncSupported(true);
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setName(WebSocketUpgradeFilter.class.getName());
registration.setOrder(Ordered.LOWEST_PRECEDENCE);
registration.setUrlPatterns(List.of("/*"));
return registration;
} Without this ServletContext attribute in Jetty 9, there would be 2 WebSocketUpgradeFilters, causing your initialization to either fail, or in your FilterRegistrationBean to add a duplicate WebSocketUpgradeFilter to a place/position that doesn't matter (as the default one is still in its early location) |
There's no need for the registration bean in Spring Boot 2.7 with Jetty 9 as the filter ordering's fine there. Regardless, thanks for sharing the tip about the attribute as it may prove useful for someone in the future who does need to tweak the filter ordering. |
With Jetty 9, the default behavior on standalone Jetty, or an embedded Jetty user that relies on the default WebApp / WebAppContext Configurations, is ...
|
While we use |
Hi,
I'm currently migrating an application to Spring Boot 3.x. This application declare a rest controller and a websocket endpoint authenticated via basic auth. But the basic auth does not work anymore on the websocket endpoint.
Sample application:
https://github.com/rcosne/ws-test
RestController: http://localhost:8080/test
Websocket endpoint: ws://localhost:8080/wstest
It seems that the whole security filter chain is skipped in this case. I've tried to declare a customer filter via
@Component
, and via aJettyServerCustomizer
, in both case, the filter is applied in the rest controller, but not in the websocket endpoint.I've also tested with Tomcat, then the basic auth works with the websocket.
Best Regards,
Rémy
The text was updated successfully, but these errors were encountered: