6

Right now, the load balancers handle https and then pass along that https to my web servers. So dealing with https double for each request. What I want to do is completely offload https so my web servers don't have to deal with it.

How do I configure Spring Security and JSP pages given that the web servers think all requests are http? Obviously I'll have to modify the <intercept-url> elements of my configuration to have their requires-channel attribute always be http or any. In my JSP pages I'll have to prepend the <c:url value=''/> links with a ${secureUrl} and ${nonSecureUrl} depending whether the resulting page needs to be https or http. Redirects from controllers need to be modified like this as well... Anything else?

Seems like quite a pain to modify all links in JSP pages to include the scheme and host too. Is there a better way to do that?

at.
  • 50,922
  • 104
  • 292
  • 461

3 Answers3

6

If you terminate SSL at the load balancer then your load balancer should send a header indicating what protocol was originally requested. For example, the F5 adds X-Forwarded-Proto.

From here you can create custom ChannelProcessors that look at this header instead of looking at request.isSecure(). Then you can continue using <intercept-url requires-channel="https"> and relative <c:url>.

The steps:

  1. Subclass SecureChannelProcessor and InsecureChannelProcessor overriding decide(). In decide() check the header sent by your load balancer.

    @Override
    public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException, ServletException {
    
      for (ConfigAttribute attribute : config) {
          if (supports(attribute)) {
              if (invocation.getHttpRequest().
                      getHeader("X-Forwarded-Proto").equals("http")) {
                  entryPoint.commence(invocation.getRequest(),
                      invocation.getResponse());
              }
          }
      }
    }
    
  2. Then set these ChannelProcessors on the ChannelDecisionManagerImpl bean using a BeanPostProcessor. See this Spring Security FAQ on why/how to use a BeanPostProcessor for this.

sourcedelica
  • 23,940
  • 7
  • 66
  • 74
2

To complete the great sourcedelica answer, here is the full code :

For Step 1 :

@Component
public class SecureChannelProcessorHack extends SecureChannelProcessor {

@Override
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException, ServletException {
    for (ConfigAttribute attribute : config) {
        if (supports(attribute)) {
            if ("http".equals(invocation.getHttpRequest().getHeader("X-Forwarded-Proto"))) {
                getEntryPoint().commence(invocation.getRequest(),
                        invocation.getResponse());
            }
        }
    }
}
}



@Component
public class InsecureChannelProcessorHack extends InsecureChannelProcessor {

@Override
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException, ServletException {
    for (ConfigAttribute attribute : config) {
        if (supports(attribute)) {
            if ("https".equals(invocation.getHttpRequest().getHeader("X-Forwarded-Proto"))) {
                getEntryPoint().commence(invocation.getRequest(),
                        invocation.getResponse());
            }
        }
    }
}
}

And step 2 :

@Configuration
public class LoadBalancerHack implements BeanPostProcessor {

@Inject
SecureChannelProcessorHack secureChannelProcessorHack;

@Inject
InsecureChannelProcessorHack insecureChannelProcessorHack;

@Value("${behind.loadbalancer?false}")
boolean behindLoadBalancer;

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (behindLoadBalancer && bean instanceof ChannelDecisionManagerImpl) {
        System.out.println("********* Post-processing " + beanName);
        ((ChannelDecisionManagerImpl) bean).setChannelProcessors(newArrayList(
                insecureChannelProcessorHack,
                secureChannelProcessorHack
        ));
    }
    return bean;
}

}
1

Looks like Grails supports this as a part of the security plugin. Checkout the bottom section of http://grails-plugins.github.com/grails-spring-security-core/docs/manual/guide/17%20Channel%20Security.html where they talk about checking request headers, which the LB will set.

grails.plugins.springsecurity.secureChannel.useHeaderCheckChannelSecurity = true
grails.plugins.springsecurity.secureChannel.secureHeaderName = '...'
grails.plugins.springsecurity.secureChannel.secureHeaderValue = '...'
grails.plugins.springsecurity.secureChannel.insecureHeaderName = '...'
grails.plugins.springsecurity.secureChannel.insecureHeaderValue = '...'
Joel
  • 449
  • 1
  • 5
  • 12