Automatic http/httpS switching with Grails

A common requirement in webapps nowadays is to switch users between a secure and insecure connection (called protocol switching). For example, maybe your user needs to enter a password, credit card number, or some other sensitive information. Of course, you (and the user) would like that information to be sent securely, which means requiring https. Assuming you’ve already got a server set up with an SSL cert and it’s ready to serve pages over SSL, you’ll need to ensure your webapp serves all secure pages over https. Unfortunately, this isn’t always as easy as it seems. But it’s not too tough either, thanks to Grails and Spring Security.
Here are the requirements for protocol switching:
- Serve secure pages using https, regardless of the link used to get to the page (i.e. http links should be redirected to https)
- Make protocol switching transparent to the majority of your application (i.e. links starting with http:// will automatically get redirected to https:// and vice versa)
- Easy configuration of which resources must be served as secure, insecure, or either (i.e. images, CSS, and JavaScript should be loaded using the same protocol the page uses to avoid nasty browser warnings (see below))
- Security and protocol switching should be handled in a way such that browsers aren’t continuously popping up warning dialogs
- Make it work in Grails
Implementation
There are several ways to go about automatic protocol switching. One of the most popular would be to use Apache and mod_rewrite. That solution works fine, but it’s not portable between different types of servers.
The solution below is pure Java, and is portable between any servlet container. By the way, this isn’t a Grails only solution – this will work with pretty much any Java web stack – the only thing that will differ is how you wire things up. In fact I’ve used this in a regular Spring MVC app with a regular Spring configuration…but I’m only going to show the Grails way to do it today.
Here’s how to get automatic protocol switching in Grails:
- Add a filter definition to web.xml that intercepts all requests and handles the protocol switching and redirecting
- Configure which URLs require which protocol
- Test!
Adding the filter definition
Spring Security has protocol switching built in, so why reinvent the wheel? (Don’t worry, you don’t need to use any other parts of Spring Security to get protocol switching.) Spring Security refers to protocol switching as Channel Security, but don’t worry, it’s the same thing.
If your Grails app isn’t already using Spring Security, add the following dependencies into grails-app/conf/BuildConfig.groovy to have Grails download the required JARs:
grails.project.dependency.resolution = {
// ...
// other settings...
// ...
dependencies {
// ...
// other dependencies...
// ...
runtime 'org.springframework.security:spring-security-core:3.0.2.RELEASE' // http -> https redirecting
runtime 'org.springframework.security:spring-security-web:3.0.2.RELEASE' // http -> https redirecting
}
}
You’ll need to add a Servlet Filter to web.xml now. There are a couple of ways to do this in Grails, namely writing a plugin that can modify web.xml dynamically, or installing the Grails templates into your app and manually editing web.xml. I had planned on doing the first option (writing a plugin), but it was overkill for this, so I decided against it. I’m glad I did. Installing the Grails templates and modifying web.xml manually is painless.
Run grails install-templates from the root of your Grails project to install the web.xml template (along with a few others). Next, edit src/templates/war/web.xml and add the filter definition and mapping in the appropriate places:
<!-- START: use SSL on secure pages --> <filter> <filter-name>channelProcessingFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <!-- END: use SSL on secure pages --> <!-- ... other filter definitions ... --> <!-- START: use SSL on secure pages --> <filter-mapping> <filter-name>channelProcessingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- END: use SSL on secure pages -->
Make sure that the filter-mapping is the first filter-mapping defined in web.xml (above charEncodingFilter and sitemesh).
Now to actually define the filter. Define it as a Spring managed bean in grails-app/conf/spring/resources.groovy:
import org.springframework.security.web.util.AntUrlPathMatcher
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource
import org.springframework.security.web.access.channel.SecureChannelProcessor
import org.springframework.security.web.access.channel.InsecureChannelProcessor
import org.springframework.security.web.access.channel.ChannelProcessingFilter
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl
beans = {
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// SPRING SECURITY (CHANNEL SECURITY)
channelDecisionManager(ChannelDecisionManagerImpl) {
channelProcessors = [new SecureChannelProcessor(),
new InsecureChannelProcessor()]
}
securityMetadataSource(DefaultFilterInvocationSecurityMetadataSource,
new AntUrlPathMatcher(),
ChannelConfig.getChannelConfig()) {
stripQueryStringFromUrls = true
}
channelProcessingFilter(ChannelProcessingFilter) {
channelDecisionManager = ref("channelDecisionManager")
securityMetadataSource = ref("securityMetadataSource")
}
}
channelProcessingFilter is the filter referenced in web.xml. It will use the channelDecisionManager to decide if the current protocol (http or https) needs to be switched to the other. And how does channelProcessingFilter know which URLs require which protocol? securityMetadataSource, of course. By default, ports 80 and 8080 are considered insecure, and 443 and 8443 are considered secure. This means that the defaults should work for both development (8080 and 8443) and production (80 and 443). If you’re curious as to the specifics of what these beans do, check their Javadocs.
Configure which URLs require which protocol
See that call above to ChannelConfig.getChannelConfig()? That’s where the configuration for URLs is stored. Create grails-app/conf/ChannelConfig.groovy:
import org.springframework.security.access.ConfigAttribute
import org.springframework.security.access.SecurityConfig
import org.springframework.security.web.access.intercept.RequestKey
class ChannelConfig {
private ChannelConfig() {} // prevent instantiation
static def getChannelConfig() {
LinkedHashMap<RequestKey,java.util.Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestKey, Collection<ConfigAttribute>>()
// resources that can be served over http or https (typically whatever the containing page is served as)
requestMap.put new RequestKey("/images/**"), [new SecurityConfig("ANY_CHANNEL")]
requestMap.put new RequestKey("/css/**"), [new SecurityConfig("ANY_CHANNEL")]
requestMap.put new RequestKey("/js/**"), [new SecurityConfig("ANY_CHANNEL")]
requestMap.put new RequestKey("/favicon.ico"), [new SecurityConfig("ANY_CHANNEL")]
// resources that must be served over https
requestMap.put new RequestKey("/signup"), [new SecurityConfig("REQUIRES_SECURE_CHANNEL")]
requestMap.put new RequestKey("/auth/**"), [new SecurityConfig("REQUIRES_SECURE_CHANNEL")]
requestMap.put new RequestKey("/admin/**"), [new SecurityConfig("REQUIRES_SECURE_CHANNEL")]
requestMap.put new RequestKey("/app/account/edituser"), [new SecurityConfig("REQUIRES_SECURE_CHANNEL")]
// resources that must be served over http (basically everything else not already listed above)
requestMap.put new RequestKey("/**"), [new SecurityConfig("REQUIRES_INSECURE_CHANNEL")] // all other pages should be served over http
requestMap
}
}
What’s happening here? First, there’s some horrible nastiness with the definition of the requestMap variable (gotta love generics). This is how your configuration is stored and the format securityMetadataSource expects. Each call to requestMap.put() specifies a URL or URL pattern (Apache Ant pattern style) and its corresponding channel security (i.e. http or https).
There are three options for channel security:
- ANY_CHANNEL – Serve the resource with either http or https – it doesn’t matter. This is good for images, CSS, and JavaScript, which should be served using the same protocol as the containing page.
- REQUIRES_SECURE_CHANNEL – Serve the resource using https. This is what will automatically redirect the user to https when needed.
- REQUIRES_INSECURE_CHANNEL – Serve the resource using http. You don’t want to serve your entire site over https, right?
In the configuration above, I first specified all the resources that are protocol agnostic – images, CSS, etc. Then in the next section I specified all of the resources that must be served securely. Finally, the /** specifies that anything else not already listed above will be served over plain http.
Note that order matters (hence the use of a LinkedHashMap that retains insertion order). Rules should be added from most specific to least specific. For example, if the /** rule was at the very top, it would match every resource request, which would be very bad.
If you want to see the debug output from Spring Security as it makes its decisions about whether or not a resource is being served over the right protocol, add the following to your Log4j config in grails-app/conf/Config.groovy:
log4j = {
// ...
// ... other logging definitions
// ...
debug 'org.springframework.security'
}
Testing
Since you’re testing your app with https, make sure to start Grails with the -https option:
grails run-app -https
This will automatically set up a fake SSL certificate for your app and run https on port 8443.
You should now be able to go to http://localhost:8080 and see that it is indeed served over http. If you have a sign up page like configured above, navigating to http://localhost:8080 should automatically redirect your browser to https://localhost:8443. Clicking on a link to https://localhost:8443/index should then automatically take you back to http://localhost:8080/index. You’ll also notice that resources like images, CSS, and JavaScript are served using whatever protocol the containing page uses.
Caveats
Did you know that if an HttpSession is created over an https connection that it won’t be available to the user when they go back to regular old http? This means that if you have your user log in using https, when they are redirected back to http, their session will be gone. This won’t be a problem for you if you plan to have users always use https once they’ve logged in. But some sites (flickr, for example) prefer to serve most of their pages using http for performance reasons once the user has logged in securely. There is a trick to allow https -> http session migration, but that’s a topic for a later blog post.
Enjoyed this post? Click to get future articles delivered by email or get the RSS feed.

Great post! And please post the https -> http session migration trick! This is a problem I’ve been dealing with for years by simply serving my whole site via https and I’d really like to know how to complete the whole picture here with sessions that persist across the secure/insecure boundary.
Todd –
Yeah, I’ve struggled with the http/httpS session stuff too. I’ll get the post up soon.
@Todd –
As a quick tip, if you start a HttpSession under http (not httpS), it will persist across to https. So you could do something like request.getSession(true) in an http request, and when you send the user to the login page that is running under https, the session would still be there. It’s the sessions that are created under httpS that aren’t available in http.
Also, I got the solution I’m going to write about from this post: http://forums.sun.com/thread.jspa?threadID=197150&start=15&tstart=0
Enjoy!
Jon
Thanks for the pointer to the Sun forum thread! I hope you’ll still post your own take on it, and perhaps a more Grails magic-type approach?
Jon, you know who this is. We need to talk about this:
dependency.resolution
Jon…
I love it…as long as Maven doesn’t need to get involved.
But when you’re not using a nice framework like grails, you can see the value of dependency resolution you need something.
Maven’s just an external framework.
Great post – thanks!
Followed your instructions and works a marvel. However I noticed that when you are submitting a POST form values from a http page to another page that gets redirected to HTTPS then the form values are lost (params empty). Works fine with GET, but not very neat.
Do you have any workarounds for this?
Cheers!
Kristo –
I know exactly what you’re talking about. I haven’t done a workaround for this, but if memory serves I think Spring Security should be handling the disappearing POST values somehow.
Have a look at the Javadocs and sourcecode for org.springframework.security.web.savedrequest.SavedRequest. I think that might be the start of the solution. SavedRequest holds all the POSTed values and is then stored in the Session. The trick is that a full Spring Security config is aware of SavedRequest and knows how to use it, but the setup I describe above doesn’t take advantage of it.
Maybe there’s already a Spring Security filter that will create and store a SavedRequest? Or maybe if not, it’d be easy enough to extend ChannelProcessingFilter to do it.
Thanks for the suggestion – will certainly look into this, once we round the other problem we’re now facing with this redirect scheme in production.
Apparently our hosting provider (mochahost, they be damned) has configured an Apache proxy to do the SSL in front of Tomcat and then redirects both HTTPS and HTTP requests to a common port in Tomcat. Therefore Tomcat is entirely unaware of whether the request came on port 80 or 443. request.isSecure() is always “false” and causes an endless redirect loop.
I’ve tried to think of any other indication of how to work out where the request originated … headers, cookies, etc.. but no luck yet. May end up changing the hosting.
Kristo –
I was just going to say that I have an Apache HTTPD server doing the SSL work in front of Tomcat like you describe, but request.isSecure() still works correctly. I believe I have 2 Connectors (I think that’s what Tomcat calls them), one for http, one for https. Apache is configured to forward to the correct one based on the protocol, which it sounds like your host is not doing. I wonder if you could just get them to add another connector?
Here’s the setup in my Tomcat server.xml:
<Connector port="8084" protocol="AJP/1.3" redirectPort="8443" enableLookups="false" proxyName="mydomain.com" proxyPort="80"/>
<Connector port="8085" protocol="AJP/1.3" redirectPort="8443" enableLookups="false" proxyName="mydomain.com" proxyPort="443"/>
Jon
I was just about to say, that having spent about many many hours and 12 emails over the last couple of days explaining mochahost exactly that solution, we finally got them out of denial and they have now made that change for me. Through waves of frustration we now have a working solution!
Thanks for the quick response though!
Kristo