Friday, July 29, 2016

Understanding websocket with the Spring framework (feat. spring-security)

I needed to learn how websockets worked in the Spring framework. I did this by researching how the different parts work together with the resulting package here.

It was a rabbit hole that turned out to be much deeper than I expected. The first step to a problem I had for instantaneous notifications on the client browser, was turning to websockets, since long-polling seemed a little backward at this stage. My first encounter was the pure JSR-356 implementation by Oracle. But I quickly realised that, because I'm already using the Spring framework, there was already spring-websocket. @Endpoint doesn't seem to play nice with Spring, so now I'm using what Spring has: @MessageMapping for receiving messages, @SendToUser for sending messages to a specific user, and @SubscribeMapping for accepting subscriptions from users. The documentation is still rather sparse even now, despite the few years it had already come into being.

Next up was the browser-end. That required a combination of STOMP and SockJS, both of which are already kind of integrated into spring-websocket. But getting the jQuery send/subscribe/callback functions correct still took a bit of time getting used to.

The websocket uses HTTP:Upgrade to convert a HTTP:// into a WS:// request. The problem came when it was time to test this with TLS. My setup involves fronting the Wildfly with Apache, and the HTTPS connection is handled by this proxy. The usual mod_proxy required an additional mod_proxy_wstunnel. While SockJS is handling the switch from HTTPS:// to WSS:// on its own, the proxy introduced a new set of problems. Apache was having difficulty translating the upgrade request. Part of the solution included this. But since I have a hundred and one issues to solve, the fix is still pending.

Another portion of the setup requires spring-security. And while I totally appreciate what resources mkyong has to offer, I'm still trying to wade through the swamplands on my own. You see, instead of tapping into the login module that spring-security had to offer, I decided to stick to my own version that I'd established before this. I realised from this post that you could manually set the authenticated user with
SecurityContextHolder.getContext().setAuthentication(authentication);

and prepare your own list of Granted Authority for it.

A static 403 error page was prepared and added into the spring-security setup. Turned out that I was forgetful enough to add RequestMethod.POST on top of the standard RequestMethod.GET for the page. Took me a while to hunt for this issue. But now, I'm still trying to figure out why is spring-security redirecting an authenticated user to this 403 page due to a POST form submission.

Now that I've gotten the static 403 page displaying properly, instead of the miserable "Request Method 'POST' not supported", I wanted to trace the next stage of the problem. In order to do my detective work, I needed to follow the trail from the output logs, and spring-security doesn't make it any easier. I got the hint eventually, that not only is it needed to add <security:debug /> into the XML file, because I'm using Log4j, I also had to add these lines in:
    <logger name="org.springframework.web.security">
        <level value="debug" />
    </logger>
Notice my mistake? I didn't for a while, wondering why is the log not outputting anything relevant. It ought to have been this instead:
    <logger name="org.springframework.security">
        <level value="debug" />
    </logger>
Great. The console was outputting a whole bunch; too much of a bunch in fact. I had to enable the log file that will be output on to disk and trace from there. Finally getting some where, I identified the offending line:
DEBUG csrf.CsrfFilter - Invalid CSRF token found for...
I was already suspicious about the CSRF portion, but thought it to be a dead-end find. Now I can be even more certain as I investigate further. Returning to the source, I continued reading up the documentation on spring-security, and came across (again) the section for CSRF protection. I noticed a particular section that discusses the use of the Multipart (file upload) content type. This jumped out at me this time round, because I realised that the form I'm encountering an issue with CSRF, involves file uploads, which certainly requires the multipart feature.

A more detailed answer (despite it being over 2 years old) was found on StackOverflow here. And it was pretty straightforward. The documentation under spring-security for adding support for CSRF to multipart forms had missed out one issue. MultipartFilter defaults to "filterMultipartResolver". At this stage, I'd already configured the CommonsMultipartResolver in my spring-mvc-context.xml as an existing bean.

Now, in order to complete the loop, I could have simply named the bean the default "filterMultipartResolver". Of course, in order to prove that the linkage was correct, I did this instead:
<filter>
    <filter-name>springMultipartFilter</filter-name>
    <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
    <init-param>
        <param-name>multipartResolverBeanName</param-name>
        <param-value>ubiomiMultipartResolver</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>springMultipartFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
which was then placed before the springSecurityFilterChain lines of configuration in my web.xml file.

I'm happy to report that the CSRF protection remains in place, and the multipart content passes through spring-security properly!

The journey continues.