at.
at.

Reputation: 52590

Offloading https to load balancers with Spring Security

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?

Upvotes: 6

Views: 6960

Answers (3)

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;
}

}

Upvotes: 3

Joel
Joel

Reputation: 459

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 = '...'

Upvotes: 1

sourcedelica
sourcedelica

Reputation: 24047

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.

Upvotes: 6

Related Questions