As most people, I have a root and servlet contexts. I need to enable websocket Stomp. I also need to send messages from the Service layer. So how do I configure?
If I put websocket:message-broker into the servlet config, then SimpMessagingTemplate is not autowired in the service layer. If I put it into the root, then it doesn't work at all, it isn't registered as an HTTP handler (even though logs say it does). If I put everything into the dispatcher context, then there is a
java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:252
For now I am going to work around it by calling SimpMessaging from Controller, and not Service, but I'd rather a better solution.
Related
In a raw Spring WebSocket application (not using sockjs/STOMP or any other middleware), how can I have Spring inject beans that have been registered in the HTTP session scope so that they can be used by code in my WebSocketHandler bean?
Note that what I am not asking is any of these questions:
How do I create beans in a scope that is accessible to all handler invocations for the same WebSocket session (e.g. as described in the answer to Request or Session scope in Spring Websocket). The beans I need to access already exist in the scope for the HTTP session
How do I (programatically) access objects in the servlet container's HTTP session storage (I haven't tried to do this, but I'm pretty sure the answer involves using an HttpSessionHandshakeInterceptor), but that doesn't get me injection of Spring scoped dependencies.
How to use a ScopedProxy to pass beans between code in different scopes (e.g. as described here); I'm already familiar with how to do this, but attempting to do so for a WebSocketHandler causes an error because the session scope hasn't been bound to the thread at the point the object is accessed.
How to access the current security principal -- again, very useful, but not what I'm currently trying to achieve.
What I'm hoping to do is provide a simple framework that allows for the traditional HTTP-request initiated parts of an MVC application to communicate directly with a WebSocket protocol (for sending simple push updates to the client). What I want to be able to do is push data into a session scoped object from the MVC controller and pull it out in the websocket handler. I would like the simplest possible API for this from the MVC controller's perspective, which if it is possible to just use a session-scoped bean for this would be ideal. If you have any other ideas about very simple ways of sharing this data, I'd also like to hear those in case this approach isn't possible.
You can also use Java API for websocket. This link https://spring.io/blog/2013/05/23/spring-framework-4-0-m1-websocket-support
explains how to do this with Spring.
Ufortunately, something like this
#ServerEndpoint(value = "/sample", configurator = SpringConfigurator.class)
public class SampleEndpoint {
private SessionScopedBean sessionScopedBean;
#Autowired
public SampleEndpoint(SessionScopedBean sessionScopedBean) {
this.sessionScopedBean = sessionScopedBean;
}
}
causes exception (because we're trying to access bean outside its scope), but for singleton and prototype beans it works well.
To work with session attributes you can modify the hanshake and pass required attributes:
public class CustomWebSocketConfigurator extends SpringConfigurator {
#Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response) {
//put attributes from http session to websocket session
HttpSession httpSession = (HttpSession) request.getHttpSession();
config.getUserProperties().put("some_attribute",
httpSession.getAttribute("some_attribute_in_http_session"));
}
}
P. S. More a comment than an answer. I just wanted to add another way of handling session attributes in websocket to your question-answer. I have been searching the web for exactly the same issue and the way showed above seems to me the most systematic approach to handling the session data in websocket.
I have a JSP-type servlet that registers a WebSocket Endpoint with the Servlet container.
I want to pass a reference of that servlet, and/or some of its objects, to the WebSocket Endpoint, so that I can use the code from that servlet, e.g. for Authentication or Session management (the servlet has its own non-Java EE Session management).
I was hoping that I could set some attribute somewhere when I call addEndpoint() on ServerContainer, because at that point I have access to the objects that I want to use later, but none of the classes that I've seen at that point have an attribute collection, e.g.
objectThatWillBeAvailableAtWebSocket.addAttribute("some.custom.object", someObject);
By the time my code reaches an ServletRequestListener, ServerEndpointConfig.Configurator, or the registered Endpoint, I do not have any reference to the original servlet that added the Endpoint.
How can I pass an Object to the WebSocket servlet? I'm running my test code in Embedded Jetty, but I'm aiming for Container-agnostic code.
Ok, my situation is much more complicated but there is an easy way to reproduce. Starting with a fresh new ASP.NET MVC 4 Web Application project and selecting Web API as a template I just add a second mvc action to the HomeController where I need to call Web API internally.
public async Task<string> TestAPI()
{
HttpServer server = new HttpServer(GlobalConfiguration.Configuration);
using (HttpMessageInvoker messageInvoker = new HttpMessageInvoker(server, false))
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:58233/api/values");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = messageInvoker.SendAsync(request, new CancellationToken()).Result;
return await response.Content.ReadAsStringAsync();
}
//server.Dispose(); - if I do that on the second request I get a "Cannot access a disposed object." exception
}
that thing works only on the first request. On subsequent requests it throws with
The 'DelegatingHandler' list is invalid because the property
'InnerHandler' of 'RequestMessageHandlerTracer' is not null. Parameter
name: handlers
I really need to use the GlobalConfiguration.Configuration here since my system is very modular/plugin based, which makes it really hard to reconstruct that configuration within the action method(or anywhere else).
I would suggest trying to re-use the HttpServer instance on secondary requests. Creating and configuring a new server on every request is not an expected usage and you are likely hitting some edge case. Either setup a DI mechanism and inject into your controller a singleton of the HttpServer, or try accessing it from some static property.
I also would suggest using new HttpClient(httpServer) instead of HttpMessageInvoker.
The same issue can occur in Web API, if you have multiple HttpServers using the same configuration object, and the configuration contains a non-empty list of delegating handlers.
The error occurs because MVC/Web API builds a pipeline of handlers on first request, containing all the delegating handlers (eg RequestMessageHandlerTracer if request tracing is enabled) linked to each other, followed by the MVC server handler.
If you have multiple HttpServers using the same configuration object, and the config object contains delegating handlers, the first HttpServer will be successfully connected into a pipeline; but the second one won't, because the delegating handlers are already connected - instead it will throw this exception on first request/initialization.
More detail on the Web API case here (which is conceptually identical, but uses different classes and would have a slightly different fix):
webapi batching and delegating handlers
In my opinion, the MVC configuration classes should be pure config, and not contain actual delegating handlers. Instead, the configuration classes should create new delegating handlers upon initialization. Then this bug wouldn't exist.
I have an ASP.NET Web API web service which throws a SerializationException in certain circumstances. The problem is that I'm unable to trap and log this exception server-side -- the only place it shows up is in the body of the HTTP response to the client.
I registered an ExceptionFilterAttribute as described in Exception Handling in ASP.NET Web API and verified that it works properly when I throw an exception within my controller. Unfortunately the SerializationException is being thrown during the response (after the controller) and appears to be completely swallowed up by ASP.NET. I also tried hooking Application_Error() in Global.asax.cs but it didn't show up there either.
How can I catch SerializationException exceptions during the Web API response?
If, instead of returning an object, you use the ApiController.CreateResponse() method and return a HttpResponseMessage you can then do response.Content.LoadIntoBufferAsync().Wait() and that will force the serialization to happen whilst you are still in the action and therefore can catch the exception.
BTW, Serialization of responses actually happens at the host layers(in HttpControllerHandler, when hosted in IIS and in HttpSelfhostServer, when hosted in SelfHost) which is way below the stack and not immediately after the response is returned from an action.
WebAPI Stack Poster: http://www.asp.net/posters/web-api/ASP.NET-Web-API-Poster-grayscale.pdf
That said, I am not able to come up with a straight forward way to achieve this. This is cumbersome, but may be override the default Xml and Json formatter's WriteToStreamAsync methods and try-catch-log any exceptions?
Alternatively, you can enable Web API Tracing which would log the exceptions happening during serialization. But yeah, if you do not know for the requests which cause the serialization errors, then you might want to enable tracing all the time which i am not sure is something you might want to do.
You can catch all Web API exceptions by registering an implementation of IExceptionHandler.
See Web API Global Error Handling
...there are a number of cases that exception filters can’t handle. For example:
Exceptions thrown from controller constructors.
Exceptions thrown from message handlers.
Exceptions thrown during routing.
Exceptions thrown during response content serialization .
One thing not mentioned in that article is that your IExceptionHandler must be registered, either by GlobalConfiguration.Configuration.Services.Add(...) or via an IoC container configured to be used by DependencyResolver.
I tried to access my application on CloudFoundry with the following configuration in the spring security xml
<intercept-url pattern="/signup*" access="permitAll" requires-channel="https" />
but it gives me error This webpage has a redirect loop
However when I changed it to requires-channel="http" I can see my page normally. In both cases I used https on my application. Is this the expected behavior ?
First of all, taking a step back, this (https://johnpfield.wordpress.com/2014/09/10/configuring-ssltls-for-cloud-foundry/) provides excellent context for the nature of the problem.
The key paragraph being
“The threat model here is that the entry point to the cloud is a high availability, secured proxy server. Once the traffic traverses that perimeter, it is on a trusted subnet. In fact, the actual IP address and port number where the Tomcat application server are running are not visible from outside of the cloud. The only way to get an HTTP request to that port is to go via the secure proxy. This pattern is a well established best practice amongst security architecture practitioners.”
Therefore, we may not want or need SSL all the way down, but read on to see how to avoid the https redirect issue when using Spring Security deployed on Cloud Foundry.
You will have a load balancer, HAProxy or some kind of proxy terminating SSL at the boundary of your Cloud Foundry installation. As a convention, whatever you are using should be configured to set X-Forwarded-For and X-Forwarded-Proto headers. The request header “X-Forwarded-Proto" contains the value http or https depending on the original request and you need to use this header parameter for your security decisions further down the stack.
The cleanest way to do this is at the container level, so that Spring Security behaves the same independent of deployment container. Some typical options to configure this are as follows
1) Tomcat
Tomcat should be configured with a RemoteIPValve as described nicely here
The good news is that the Java buildpack for Cloud Foundry already does this for you as seen here
2) Spring Boot (Embedded Tomcat)
Because Tomcat is embedded, the Tomcat config in the Java buildpack will not be activated (see the buildpack Detection Criterion), and therefore some internal Spring Boot configuration is required. Luckily, it’s pretty trivial to configure as you would expect with Spring Boot and you can switch on Tomcat’s RemoteIPValve as explained here by simply defining
server.tomcat.remote_ip_header=x-forwarded-for
server.tomcat.protocol_header=x-forwarded-proto
Both approaches lead to the same outcome of the Tomcat valve overriding the ServletRequest.isSecure() behaviour so that the application has no knowledge of the usage of any proxying. Note that the valve will only be used when the “X-Forwarded-Proto" header is set.
Alternatively, if you really want to go low-level you can dig into the guts of Spring Security, as demonstrated here. As part of that effort, there are some useful findings on how to make the “X-Forwarded-Proto" header available via the Servlet API for other containers (WebLogic, JBoss, Jetty, Glassfish) shared on the comments of https://github.com/BroadleafCommerce/BroadleafCommerce/issues/424
As an additional note, CloudFlare can also act as the SSL-terminating reverse proxy (this is the recommended approach via PWS as discussed here) and it does indeed forward the relevant headers.
References
https://stackoverflow.com/a/28300485/752167
http://experts.hybris.com/answers/33612/view.html
https://github.com/cloudfoundry/java-buildpack/commit/540633bc932299ef4335fde16d4069349c66062e
https://support.run.pivotal.io/entries/24898367-Spring-security-with-https
http://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-servlet-containers.html#howto-use-tomcat-behind-a-proxy-server
I have the same issue when I tried to secure my pages with HTTPS using Spring Security.
From the discussion on CloudFoundry Support, seems they "terminate SSL connections at the router". See "Is it possible to visit my application via SSL (HTTPS)?".
And after more than a year, no further information I can find regarding this issue.
In case it's still useful ... I found this post gave the clue to solve something similar to this.
The problem was the org.springframework.security.web.access.channel.SecureChannelProcessor bean was using ServletRequest.isSecure() to decide whether to accept the connection or redirect, which was getting confused inside the cloud.
The following override to that bean seemed to do the job under BlueMix - not sure if the $WSSC request header will apply to all environments.
#Component
public class ChannelProcessorsPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (bean instanceof SecureChannelProcessor) {
final SecureChannelProcessor scp = (SecureChannelProcessor) bean;
return new ChannelProcessor() {
#Override
public void decide(FilterInvocation invocation,
Collection<ConfigAttribute> config) throws IOException,
ServletException {
HttpServletRequest httpRequest = invocation.getHttpRequest();
// Running under BlueMix (CloudFoundry in general?), the
// invocation.getHttpRequest().isSecure() in SecureChannelProcessor
// was always returning false
if ("https".equals(httpRequest.getHeader("$WSSC"))) {
return;
}
scp.decide(invocation, config);
}
#Override
public boolean supports(ConfigAttribute attribute) {
return scp.supports(attribute);
}
};
}
return bean;
}
#Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
return bean;
}
}