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.

Thursday, June 16, 2016

Upgrading from Wildfly 9 + Hibernate 4.3 to Wildfly 10 + Hibernate 5

TL;DR - Exclude Hibernate libraries in POM using <scope>provided</scope> when deploying to Wildfly.

When this project first started several months ago, Wildfly 9 and Hibernate 4.3 was the most current stable releases. Things have changed however, and it's time to move on. The migration provided a couple of learning points, mostly to do with ensuring the project was compatible with the updated versions.

After having Wildfly 10 up and running, I attempted to deploy the WAR file as-is without upgrading to Hibernate 5. The major error that returned from the deployment was about the java.lang.AbstractMethodError similar to this problem. Exactly in SessionFactoryImpl, exactly on line 278. Thinking that there might be a version mismatch (using 4.3 vs 5), I edited the POM to bring all Hibernate libraries up a major version. This was not the case.

 The JPA reference for Wildfly 10 here suggested that Hibernate 5 requires a change to the persistence.xml such that, instead of org.hibernate.ejb.HibernatePersistence
the class name for the <provider> tag should be org.hibernate.jpa.HibernatePersistenceProvider for new deployments. A minor misstep on my part was ignoring the sub-package change of .ejb to .jpa and merely added in "Provider" at the end. That aside, this did not help whatsoever. The documentation goes on to indicate that it's actually possible to leave this value out entirely. So I did.

Next, I even dug into the downloaded zip for Wildfly 10 to check out the libraries they used and made sure to match up what goes into my POM version for version. It eventually caught on in my head that, Wildfly is using its own version of Hibernate to run my package, but encounters the error because I'm providing my own version. I'd previously specified an explicit exclusion in Wildfly for dom4j, but I felt that the complete set of Hibernate 5 library JAR files would be better off being used in conjunction with what Wildfly is familiar with.

The Hibernate dependencies in the POM was modified to have <scope>provided</scope> for this change. And it did the trick! My local Jetty build would have to add these dependencies in, of course.

Thursday, May 19, 2016

Use jMimeMagic instead because Wildfly conflicts with Tika

There was a need to retrieve images from external sites, so after some research, I thought that Tika would do the trick. I had preferred content type detection via magic numbers over file extension only. Unfortunately, the library had a very long list of dependencies. Jetty didn't complain, but the moment I deployed the WAR file on to Wildfly, it encountered a whole bunch of problems, including this. I didn't think the headache was worth the it and sought for an alternative. Google returned this article which had a pretty comprehensive list of libraries. Despite the smaller footprint of mime-util, I decided to go for the more recent jMimeMagic (added TIFF support, last updated 12 Dec 2014, as of today) instead.

Magic parser = new Magic();
MagicMatch match = parser.getMagicMatch(new File("image.jpg"));
System.out.println(match.getMimeType());

That's all there is to using it.

At least Wildfly accepted the new WAR file. We'll have to continue monitoring the situation for this.