Spring WebSocket Connecting with SockJS to a different domain - spring

WebSockets in Spring is a rather new topic that I;m tiring to find a bit more.
My problem is with connecting to a service from a different domain, I'm working on with Lineman building the front-end side and Spring Boot when doing the back-end side, with that I have these apps on two different ports : 8000 and 8080 on localhost.
I had issues with the 'Access-Control-Allow-Origin' header but I have resolved it by adding a filter on the server side which added the allowed origin to the header. After this I started to get the following error on connection:
GET http://localhost:8080/socket/info 403 (Forbidden)
AbstractXHRObject._start # sockjs-0.3.4.js:807
(anonymous function) #sockjs-0.3.4.js:841
I don't have Spring Security in the project so this is not an authorization issue, the error points to sockJS :
that.xhr.send(payload); - where payload is never defined.I tried but couldn't find the root of the call where is may began.
I was thinking if I need to add some additional information to either SockJS and Stomp when setting the connection, but there is not much of examples and notes in both wiki pages of this tools.
Bellow you will find the connection JS code.
var socket = new SockJS("http://localhost:8080/socket");
client = Stomp.over(socket);
client.connect({'login': BoatsGame.userName,
'passcode': 'guest'},
function (frame) {
....
The Server Side has a MessageBroker configured :
#Configuration
#EnableWebSocketMessageBroker
public class MessageBrokerConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
//config.enableStompBrokerRelay("/queue", "/topic");
config.enableSimpleBroker("/queue", "/topic","/user");
config.setApplicationDestinationPrefixes("/BoatBattleGame");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/socket").withSockJS();
}
}
I Also tried setting up a MessageHandler as it has the option to set OriginAllowe when configuring, but I'm not sure how it is connected to the broker.
Last think, this setup works correctly when running on one port.

Jax's anwesr was correct :)
The registerStompEndpoints method gives us the opportunity to set the Allowed Origins.
We need to add it before the "withSockJs()" option.
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/BO/socket").setAllowedOrigins("*").withSockJS();
}

To anyone getting to this ticket because of the 403 Forbidden answer when trying to connect through a SockJsClient to a different domain:
The problem arises when trying to make a GET to the /info Url, as part of the handshaking. The response actually returns a 200 via WGET as well as via browser. Only through SockJsClient it doesn't work.
After trying different solutions, the only one that really fixed the issue is to write a class that implements Transport and InfoReceiver. In this way the developer can directly handle this part of the handshake.
Basically you make the work in the executeInfoRequest() method:
#Override
public String executeInfoRequest(URI infoUrl, HttpHeaders headers) {
HttpGet getRequest = new HttpGet(infoUrl); // eventually add headers here
HttpClient client = HttpClients.createDefault();
try {
HttpResponse response = client.execute(getRequest);
List<String> responseOutput = IOUtils.readLines(response.getEntity().getContent());
return responseOutput.get(0);
} catch (IOException ioe) {
...
}
}
I defined TransportType.XHR as transport type.

In my case, I had to add these configuarations to get SockJS / STOM to work with CORS:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer
{
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(false)
.maxAge(3600)
.allowedHeaders("Accept", "Content-Type", "Origin",
"Authorization", "X-Auth-Token")
.exposedHeaders("X-Auth-Token", "Authorization")
.allowedMethods("POST", "GET", "DELETE", "PUT", "OPTIONS");
}
}

i found this solution by creating a Filter
package com.diool.notif.config;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class SimpleCORSFilter implements Filter {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(SimpleCORSFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
LOGGER.info("Initilisation du Middleware");
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest requestToUse = (HttpServletRequest)servletRequest;
HttpServletResponse responseToUse = (HttpServletResponse)servletResponse;
responseToUse.setHeader("Access-Control-Allow-Origin",requestToUse.getHeader("Origin"));
filterChain.doFilter(requestToUse,responseToUse);
}
#Override
public void destroy() {
}
}

Related

CORS problems with Springboot and Angular Websocket

I have two service to build a Spring Boot application
But I always got CORS problems like this
Access to XMLHttpRequest at '.../websocket-cr/info?t=1581585481804' from origin'http://localhost:8080'
has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin'
header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials.
Springboot service on 8082
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocketcr").
setAllowedOrigins("http://localhost:8080").withSockJS();
}
Angular service on 8080
var socket = new SockJS('http://localhost:8080/websocket-cr');
//socket.withCredentials = true ;
stompClient = Stomp.over(socket);
I have tried
setAllowedOrigins("http://localhost:8080").withSockJS();
setAllowedOrigins("*").withSockJS();
or use CORS Anywhere in javascript
var socket = new SockJS('http://cors-anywhere.herokuapp.com/http://localhost:8082/websocket-cr');
socket.withCredentials = true ;
and what is the best way to done that
should I make angular proxy to my backend server?
or it is ok by setAllowedOrigins('host:port')
In your Main class of the SpringBoot service , inject the below bean,it will work
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer () {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedHeaders("*")
.allowedOrigins("*");
}
};
}
Sorry for coming late to this Party.
I am using here STOMP JS in angular 8 with springboot
working demo
you need to add WebSocketConfig class to configure things for Socket and For controller separately if you need it.
Configuration Class
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
#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("*")
//.setAllowedOrigins("http://localhost:4200").setAllowedOrigins("http://localhost:8081")
.withSockJS();
}
}
ref Another Help from Spring people
In Controller Class just add
#Controller
#CrossOrigin(origins = "*")
public class LogsController {
..
}
And answer will be updated for authentication/authorization sooner and later.
Simply just add #CrossOrigin annotation on top of the class. It's work perfectly. For example:
#RestController
#CrossOrigin(origins = "*")
public class YourController {
.....
}

can i connect client server(localhost:8082) and websocket(localhost:8080)?

My problem is I need to connect different port or server!
websocket server(localhost:8080) client server(localhost:random)
(failed: Error during WebSocket handshake: Unexpected response code: 403)
why?? I'm tired...
I already tried same port and I can success!
I can connect client(localhost:8080) and websocekt(localhost:8080).
I want to use websocket using (server side: java sts, tomcat 8.0).
questions
1. websocket can connect only same server and port???!!!!(no! please!)
2. If I can... what's the problem :(? Do u have any example?
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("*");
}
#Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
Sounds like this is related to CORS https://en.wikipedia.org/wiki/Cross-origin_resource_sharing.
I assume you are using spring websocket as you didn't mention.
You will need to disable same origin policy in your websocket config class as below example,
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
}
#Override
protected boolean sameOriginDisabled() {
//disable CSRF for websockets
return true;
}

SpringBoot Rest API custom authentication

I build a Rest Api using SpringBoot and the authentication I implemented using Firebase.
My problem right now is that I want to have control of the client applications that will access my application. The problem of using SpringSecurity is that as far as I know I have to do the authentication for it and I just want to "allow the client application."
Does anyone have any idea how to do?
Provide a unique key to your client. Which your microservice recognises and authenticates any request based on that key. This can be also given as a request parameter.
let say you add your key into a parameter called my-key, now before working on your logic inside you spring-boot app validate your key. like this -
your Rest Controller would look like this-
#RestController
class MyRest{
private static final String KEY = "someValue";
#RequestMapping("/some-mapping")
public #ResponseBody myMethod(#RequestParam(value="my-key", required=true) String key){
if(!validateRequest(key)){
//return error as response
}
System.out.println("Key Validation Successful!");
//here goes your logic
}
private boolean validateRequest(String key){
return key.equals(KEY);
}
}
in order to access this rest use - http://your-host:port/some-mapping?my-key=someValue
If you want to allow some of the clients to bypass the authentication, have a list of whitelisted IP addresses and check the IP of each incoming request. if the IP is in the list of whitelisted APIs, no need to authenticate.
Use HttpServletRequest.getRemoteAddr() to get the IP address.
Solution 1
Custom interceptor MyHandlerInterceptor.java:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class MyHandlerInterceptor implements HandlerInterceptor {
private static final String YOUR_KEY = "KEY_VALUE";
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String key = request.getHeader("X-Key");
boolean isValid = YOUR_KEY.equals(key);
if (!isValid) {
//invalid key
response.setStatus(401);
PrintWriter writer = response.getWriter();
writer.write("invalid key");
}
return isValid;
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
}
}
Configure interceptor WebConfig.java:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyHandlerInterceptor());
}
}

Spring catch all route for index.html

I'm developing a spring backend for a react-based single page application where I'm using react-router for client-side routing.
Beside the index.html page the backend serves data on the path /api/**.
In order to serve my index.html from src/main/resources/public/index.html on the root path / of my application I added a resource handler
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/").addResourceLocations("/index.html");
}
What I want to is to serve the index.html page whenever no other route matches, e.g. when I call a path other than /api.
How do I configure such catch-all route in spring?
Since my react app could use the root as forward target this ended up working for me
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/{spring:\\w+}")
.setViewName("forward:/");
registry.addViewController("/**/{spring:\\w+}")
.setViewName("forward:/");
registry.addViewController("/{spring:\\w+}/**{spring:?!(\\.js|\\.css)$}")
.setViewName("forward:/");
}
}
To be honest I have no idea why it has to be exactly in this specific format to avoid infinite forwarding loop.
I have a Polymer-based PWA hosted inside of my Spring Boot app, along with static web resources like images, and a REST API under "/api/...". I want the client-side app to handle the URL routing for the PWA. Here's what I use:
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* Ensure client-side paths redirect to index.html because client handles routing. NOTE: Do NOT use #EnableWebMvc or it will break this.
*/
#Override
public void addViewControllers(ViewControllerRegistry registry) {
// Map "/"
registry.addViewController("/")
.setViewName("forward:/index.html");
// Map "/word", "/word/word", and "/word/word/word" - except for anything starting with "/api/..." or ending with
// a file extension like ".js" - to index.html. By doing this, the client receives and routes the url. It also
// allows client-side URLs to be bookmarked.
// Single directory level - no need to exclude "api"
registry.addViewController("/{x:[\\w\\-]+}")
.setViewName("forward:/index.html");
// Multi-level directory path, need to exclude "api" on the first part of the path
registry.addViewController("/{x:^(?!api$).*$}/**/{y:[\\w\\-]+}")
.setViewName("forward:/index.html");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/webapp/");
}
}
This should work for Angular and React apps as well.
Avoid #EnableWebMvc
By default Spring-Boot serves static content in src/main/resources:
/META-INF/resources/
/resources/
/static/
/public/
Take a look at this and this;
Or keep #EnableWebMvc and override addViewControllers
Did you specify #EnableWebMvc ? Take a look a this: Java Spring Boot: How to map my app root (“/”) to index.html?
Either you remove #EnableWebMvc, or you can re-define addViewControllers:
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
}
Or define a Controller to catch /
You may take a look a this spring-boot-reactjs sample project on github:
It does what you want using a Controller:
#Controller
public class HomeController {
#RequestMapping(value = "/")
public String index() {
return "index";
}
}
Its index.html is under src/main/resources/templates
I use react and react-router in my spring boot app, and it was as easy as creating a controller that has mapping to / and subtrees of my website like /users/**
Here is my solution
#Controller
public class SinglePageAppController {
#RequestMapping(value = {"/", "/users/**", "/campaigns/**"})
public String index() {
return "index";
}
}
Api calls aren't caught by this controller and resources are handled automatically.
Found an answer by looking at this question
#Bean
public EmbeddedServletContainerCustomizer notFoundCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/"));
}
};
}
Another solution (change/add/remove myurl1, myurl2, ... with your routes):
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
#Controller
public class SinglePageAppController {
/**
* If the user refreshes the page while on a React route, the request will come here.
* We need to tell it that there isn't any special page, just keep using React, by
* forwarding it back to the root.
*/
#RequestMapping({"/myurl1/**", "/myurl2/**"})
public String forward(HttpServletRequest httpServletRequest) {
return "forward:/";
}
}
Note: Using public String index() also works fine, but only if you use templates. And the use of WebMvcConfigurerAdapter is deprecated.
To answer your specific question which involves serving up the Single Page App (SPA) in all cases except the /api route here is what I did to modify Petri's answer.
I have a template named polymer that contains the index.html for my SPA. So the challenge became let's forward all routes except /api and /public-api to that view.
In my WebMvcConfigurerAdapter I override addViewControllers and used the regular expression: ^((?!/api/|/public-api/).)*$
In your case you want the regular expression: ^((?!/api/).)*$
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/{spring:^((?!/api/).)*$}").setViewName("polymer");
super.addViewControllers(registry);
}
This results in being able to hit http://localhost or http://localhost/community to serve up my SPA and all of the rest calls that the SPA makes being successfully routed to http://localhost/api/posts, http://localhost/public-api/posts, etc.
After lot of tries I've found the following solution as most simple one. It will basically bypass all the Spring handling which was so difficult to deal with.
#Component
public class StaticContentFilter implements Filter {
private List<String> fileExtensions = Arrays.asList("html", "js", "json", "csv", "css", "png", "svg", "eot", "ttf", "woff", "appcache", "jpg", "jpeg", "gif", "ico");
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String path = request.getServletPath();
boolean isApi = path.startsWith("/api");
boolean isResourceFile = !isApi && fileExtensions.stream().anyMatch(path::contains);
if (isApi) {
chain.doFilter(request, response);
} else if (isResourceFile) {
resourceToResponse("static" + path, response);
} else {
resourceToResponse("static/index.html", response);
}
}
private void resourceToResponse(String resourcePath, HttpServletResponse response) throws IOException {
InputStream inputStream = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream(resourcePath);
if (inputStream == null) {
response.sendError(NOT_FOUND.value(), NOT_FOUND.getReasonPhrase());
return;
}
inputStream.transferTo(response.getOutputStream());
}
}

How do I send a message in an Spring ApplicationListener (SessionConnectedEvent)

I'm using Stomp over SockJS with Spring messaging. I'm trying to send a message to all logged in users when a new user is connected. So first off here's my listener:
#Component
public class SessionConnectedListener implements ApplicationListener<SessionConnectedEvent> {
private static final Logger log = LoggerFactory.getLogger(SessionConnectedListener.class);
#Autowired
private SimpMessagingTemplate template;
#Override
public void onApplicationEvent(SessionConnectedEvent event) {
log.info(event.toString());
// Not sure if it's sending...?
template.convertAndSend("/topic/login", "New user logged in");
}
}
My WebSocket configurations
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("chat").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
}
My JS config
var socket = new SockJS('/chat');
stompClient = Stomp.over(socket);
stompClient.connect({}}, function(frame) {
// ... other working subscriptions
stompClient.subscribe("/topic/login", function(message) {
console.log(message.body);
});
});
My problem here is that my template.convertAndSend() doesn't work in the ApplicationListener. However, if I put it in a Controller method annotated with #MessageMapping, it will work and I will have a console log client side.
So my question is : Can template.convertAndSend() work in an ApplicationListener? If so, how? or am I missing something?
Thanks for the help!
PS : my log.info(event.toString()); works in the ApplicationListener so I know I'm getting into the onApplicationEvent() method.
Sending messages with the template within an ApplicationListener should work. Please check this Spring WebSocket Chat sample for an example.
Ok! So weird as it may be, I had my Listener in the following package:
package my.company.listener;
But because of a configuration I have in my App context, the convertAndSend() method wasn't working.
#ComponentScan(basePackages = { "my.company" }, excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern = { "my.company.web.*" }))
However when I moved my Listener (Annotated with #Component) to the web sub-package, it worked!
package my.company.web.listener;

Resources