When connecting to a WebSocket from my frontend (using Stomp for Dart):
client = await connect(uri);
The client sends this key in the header:
Sec-WebSocket-Key: ydWCNEacB1ZqiHCH1Ip+vo4mhOw=
And receives a key back from the Spring WebSocket:
Sec-WebSocket-Accept: KVqExHty0Y5/fze11/EAhg==
This causes the client to throw:
WebSocket connection to 'ws://user:pass#localhost:8080/ws'
failed: Error during WebSocket handshake: Incorrect
'Sec-WebSocket-Accept' header value
I don't know how to calculate the correct response. But either way the client doesn't accept it for some reason.
How do I approach this problem? What could be causing the problem and how might I solve it?
Thanks
Edit: added websocket config:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app").enableSimpleBroker("/topic");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*");
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
}
}
Also
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages.anyMessage().authenticated();
}
}
Edit: I found a wiki that describes how to calculate 'Sec-WebSocket-Key'. Following the guide below, it does look like the key is incorrect. This doesn't seem likely though or many more would be having issues.
https://en.wikipedia.org/wiki/WebSocket
Related
I'm trying to test a SpringBoot websocket using Postman. I'm able to successfully connect Postman to the endpoint, but I can't figure out how to test a topic subscription.
I've got a pretty standard websocket config:
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOriginPatterns("*");
registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();
}
}
The connection to /ws endpoint works fine:
Now I'd want to test a subscription to some topic /topic/x/y, just as I'm doing in the Angular frontend, but I didn't find any useful info about this, not even in Postman docs.
Could such thing be done? And, if it could, how?
I am implementing websockets to Spring App with sock.js + stomp.js on the client app.
When trying to connect I am getting the error:
Access to XMLHttpRequest at 'http://localhost:8080/ws/tracker/info?t=...' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
And in my WebsocketConfiguration :
#Configuration
#EnableWebSocketMessageBroker
#Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebsocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic/", "/queue/");
registry.setApplicationDestinationPrefixes("/app");
}
}
Client libraries:
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.4.0/sockjs.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.js"></script>
Client connection:
var socket = new SockJS('http://localhost:8080/websocket/tracker');
I have already implemented websockets in one of the earlier projects, so this is all copied from it, though I can't remember (nor find) how to solve this error.
Check if you have ClientForwardController, mapping should be like this:
#GetMapping(value = {"/{path:[^\\.]*}", "/{path:^(?!websocket).*}/**/{path:[^\\.]*}"})
public String forward() {
return "forward:/";
}
Despite having the following config, accessing http://localhost:8080/rooms/rooms.json gives me a CORS error - No 'Access-Control-Allow-Origin' header is present on the requested resource.
I have no problem to request any other path which is mapped by controller. What is the problem with static resources? How to allow cors request or exclude the resource paths without spring security?
Spring Boot 2.0.5
Spring Boot Web Starter 2.0.5
#Configuration
#EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/rooms/**")
.addResourceLocations("classpath:/rooms/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
#Override
public void addCorsMappings(final CorsRegistry registry) {
registry.addMapping("/**");
}
}
I got it working with the following configuration bean:
#Configuration
public class StaticResourcesCorsConfig
{
#Bean
public WebMvcConfigurer corsConfigurer()
{
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
}
Note, that Spring Boot will only send the Access-Control-Allow-Origin header back on a GET request, if the Origin-header is present on the request.
Update addCorsMappings like below it could work
#Configuration
#EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/rooms/**")
.addResourceLocations("classpath:/rooms/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
#Override
public void addCorsMappings(final CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("POST", "GET")
//.allowedHeaders("header1", "header2", "header3")
//.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
}
}
Add #CrossOrigin(value = "*") to your controller class. You can replace * with any particular URL in case to allow that origin only.
#CrossOrigin(origins = "http://localhost")
#GetMapping("/rooms/")
public Object rooms() {
// your implementation
}
You can do in this way.
https://www.viator.com/orion/nova/public/mmblite/styles-53929dcb.css
Experienced the same problem actually, but found the root cause and a solution.
Your request was most probably cached by intermediary: load balancer, CDN or caching HTTP server in front of your application as regular non-CORS request. Then you have sent request with Origin:, but the intermediary returned you the same cached response because from point of view of the cache responses by default are identified by /path + METHOD + Host:-header which were the same. To tell caches that the request with Origin: and the regular request (without Origin: need to be cached as independent entries in any cache we need Vary: Origin header in both responses. This was fixed/implemented in Spring 5.2.x (in my case it was Spring 5.1.9), in your case it was 5.0.9 (as dependency of Spring Boot 2.0.5.). Once I upgraded to Spring 5.2.0 all was fixed once caches on the intermediary had expired. I recommend to upgrade beyond 5.2.6 (cause there were further changes in CORS handling, which are nice to have).
here is the line (which made the difference) they (Pivotal) commited into Spring: https://github.com/spring-projects/spring-framework/commit/d27b5d0ab6e8b91a77e272ad57ae83c7d81d810b#r36264428
and their bug description: https://github.com/spring-projects/spring-framework/issues/22273
I have an application that includes a Spring cloud gateway that sits in front of an app which (among other things) supports web socket connections (sockJS). The gateway does a simple url rewrite when it forwards to the app. The two are currently running Spring-Boot 2.0.5.RELEASE and Spring-Cloud Finchley.RELEASE. According to the source I pulled down, this should be using spring-websockets-5.0.9.
When I try to upgrade to 2.1.2.RELEASE and Greenwich.RELEASE for Spring-Boot and Spring-Cloud respectively, my websocket connections start failing because an extra Access-Cloud-Allow-Origin is being injected into the response.
My gateway has a simple CORS filter like this (the values are constants and not relevant):
#Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
Mono<Void> result;
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
ServerHttpResponse response = ctx.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
headers.add("Access-Control-Max-Age", MAX_AGE);
headers.add("Access-Control-Allow-Headers",ALLOWED_HEADERS);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
result = Mono.empty();
} else {
result = chain.filter(ctx);
}
} else {
result = chain.filter(ctx);
}
return result;
};
}
And my web socket config on the downstream app is simply this:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("*")
.withSockJS();
}
}
If I comment out the .setAllowedOrigins("*") in the registerStompEndpoints method, I correctly get 403 access denied responses, and the response only has the Access-Control-Allow-Origin header as injected by the gateway.
With the method in place as shown here, the websocket response completes as expected with a success response to the caller, but the response header contains both the access control header injected by the gateway plus another Access-Control-Allow-Origin header which is set to the value of the caller (in my case, http://localhost:4200 for the front-end application.) None of the other access control headers are duplicated.
How can I configure the Spring websocket message broker to not inject the Access-Control-Allow-Origin header? This was working, and still works if I roll back to 2.0.5/Finchley.
I faced this issue recently and I was able to resolve it by calling setSupressCors method. The documentation says that
This option can be used to disable automatic addition of CORS headers for SockJS requests.
Here is a code sample:
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketMessageBrokerConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket/handshake")
.setAllowedOrigins("*")
.withSockJS()
.setSupressCors(true);
}
}
I have an endpoint (/create) which has some logic and it takes 3-4 min to process so I used rabbitmq and as soon as the endpoint receive the request it takes the body and post the message in rabbitmq, the listener listens to the message and process the request now I want to notify the user that his request is successfully processed.
Is websocket correct choice for this requirement
Is there other better way through which i can achieve my goal?
So I went forward with websocket since I am using oauth based authentication I am unable to get web-socket work
Here is my code I have written:
SocketConfig.java
#Configuration
#EnableWebSocketMessageBroker
public class SocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic","/secured/queue");
//config.setApplicationDestinationPrefixes("/app");
//config.setUserDestinationPrefix("/secured/user");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/secured/messagereg").setAllowedOrigins("*").withSockJS();
}
SocketHandler.java
#Configuration
public class SocketHandler extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected boolean sameOriginDisabled() {
return true;
}
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/secured/**", "/secured/**/**").authenticated()
.anyMessage().authenticated();
}
}
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Profile("!test")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Auth0PropertyConfig config;
#Override
protected void configure(HttpSecurity http) throws Exception {
JwtWebSecurityConfigurer
.forRS256(config.getAudience(), config.getIssuer())
.configure(http)
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
}
clientCode
const socket = new SockJs("/secured/messagereg/?access_token="+token);
this.setState({ clientRef: Stomp.over(socket) }, () =>
this.state.clientRef.connect({},
frame => {
this.setState({ connection: true });
this.state.clientRef.subscribe("/user/secured/queue/reply", message => {
console.log("asd received ----------" + message.body);
this.setState(prevs => ({
message: [...prevs.message, message]
}));
});
},
error => {
console.log("Stomp protocol error." + error);
}
)
);
I am getting 401 unauthorized while connecting to socket.
In my opinion: a push messaging pattern (for example using STOMP) is suitable for this scenario, but that ultimately depends on your architectural principles. You could also poll the server for result (using REST API) which has both advantages (shared security architecture) and disadvantages (client code, traffic, and reaction-time overheads).
Answer:
In order to get your code working, I think you need one more method in your SocketConfig.java, which will hook into your OAUTH filter (or whatever method you may have in place).
Important - websocket auth does not reuse existing Spring Security context. That's why you need to implement auth again, for example in the SocketConfig class using the WebSocketMessageBrokerConfigurer's method configureClientInboundChannel.
The following example assumes you have already obtained the OAUTH token previously, and it's only used to reauthenticate the websocket connection. Setting the user reference in StompHeaderAccessor (3rd last line) will enable your code to send a message to the correct user.
It also requires that the OAUTH token is set in the message header, as opposed to the endpoint parameter in your example. I think that may be safer for websocks messaging as the message itself is encrypted on protocol level if you use wss.
#Autowired
private YourOauthService auth;
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message,
StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String token = accessor.removeNativeHeader("Authorization").get(0);
Authentication user = auth.getAuthentication(token);
accessor.setUser(user);
}
return message;
}
});
}
I found some more interesting examples in https://robertleggett.wordpress.com/2015/05/27/websockets-with-spring-spring-security/