URLRewrite is one of the greatest things since bread was first sliced. To get one in your Maven 2 project, you will need to in the very least take the following steps (complete details are available in the URLRewrite manual, of course):
Add this dependency to your pom.xml:
<dependency> <groupId>org.tuckey</groupId> <artifactId>urlrewritefilter</artifactId> <version>3.2.0</version> <scope>runtime</scope> </dependency>
Add a filter definition to web.xml
<filter> <filter-name>UrlRewriteFilter</filter-name> <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class> </filter>
A then a filter mapping to web.xml:
<filter-mapping> <filter-name>UrlRewriteFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Finally, create a file named urlrewrite.xml and place it in the WEB-INF directory (this can be customized, if desired).
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN" "http://tuckey.org/res/dtds/urlrewrite3.0.dtd"> <urlrewrite default-match-type="wildcard"> ... RULES WILL GO IN HERE ... </urlrewrite>
That's all of the plumbing that's necessary. If URLRewrite filter is set up correctly, then you should be able to see a URLRewrite status page on localhost:{YOUR_PORT}/rewrite-status . This page will be extremely useful as you develop rules (if you have an error in your urlrewrite.xml file, then the status page will tell you what it is).
SSL Redirection with Amazon's Elastic Load Balancer (ELB) and URLRewrite
Now comes the fun part. URLRewrite is set up and working and your application is deployed in EC2 and you have an ELB configured to listen for both HTTP and HTTPS traffic (on ports 80 and 443, respectively). ELB handles all of the HTTPS certificate shenanigans for you.
Now you want to automatically redirect users who visit the HTTP site to the HTTPS site. ELB does not have this capability at the present time, unfortunately. You cannot use the standard servlet mechanism to ensure a confidential transport because the traffic between the ELB and your servlet container is HTTP.
There are a few options, one of which is to move the HTTPS certificate down to your servlet container, etc. That would be a respectable option - maybe even the best option - but let's assume for the sake of a blog post that you would like have the ELB handle the SSL.
To implement the redirection, we will take advantage of a header that the ELB adds to forwarded requests - "X-Forwarded-Proto". It's value will be "HTTPS" or "HTTP" depending on the client's original request. URLRewrite has the ability to active a rule depending on a header value, and so here you are - HTTPS redirection:
<rule match-type="regex">
<condition type="header" operator="notequal" name="X-Forwarded-Proto">^HTTPS$</condition>
<from>^.*$</from>
<to type="permanent-redirect" last="true">https://%{server-name}%{request-uri}</to>
</rule>
That's it. Non-HTTPS traffic will be permanently redirected to the HTTPS site and HTTPS traffic will flow to your application unimpeded.
Development Mode
The above works fine for production mode, but when developing we might not have HTTPS enabled. To get around that, you can add a condition to your rule as follows:
<condition type="port" operator="notequal">8080</condition>
(add it before the "from" element). You can add multiple conditions, if necessary. For example, you can also add a condition that the server-name is not equal to "localhost".
Miscellaneous
In the example above, I have assumed that URLRewrite was added to the WAR file of the application being developed. This is not strictly necessary. Tomcat, for instance, will allow you to add the filter to a global web.xml that applies to all applications. You would then need to add the URLRewrite jar to the appropriate directory. This could be useful if you have a lot of applications and you want them to use the same rules.
It is often convenient to use URLRewrite to add a version identifier (determined at build time) to each URL under a certain path encoded with response.encodeURL (including the JSTL c:url). Doing so allows you to "bust the cache" when a new version of the application is deployed. In successive testing rounds, we've often had to tell testers to refresh pages or empty browser caches in order to get the latest bug fixes. In a future blog post I would like to document a rule for this.
Great article. I am having issues getting it to work though. I used this example but am trying to do it at the tomcat level rather than the app level. I have ssl uncommented and a keystore included for ports 443. I am not running as root so that gives me permission issues. I am doing this on a Beanstalk EC2 not a plain one so the user is ec2-user not root. I put the xml in the /opt/tomcat7/conf dir and the lib in the /opt/tomcat7/lib dir. I assume that is correct. I am allowing ELB to handle ssl, so do I not need the setting in my server.xml of tomcat? even though the rest of the config is in tomcat? Any suggestions would be appreciated.
ReplyDelete