简体   繁体   中英

Spring Boot with Spring Security behind Load Balancer: redirect HTTP to HTTPS

I'm using Elastic Beanstalk to deploy a Spring Boot web server, running behind an application load balancer (Elastic Load Balancer). A business rule is that this web server should only be contacted through HTTPS. So any HTTP request must first be forwarded to HTTPS.

Based on this article from Amazon, I should simply inspect the x-forwarded-for and x-forwarded-proto headers, which are set by the load balancer. These headers contain information about the original request, made by the client to the load balancer.

So I started looking for built-in ways for Spring Boot (I'm using the built-in Tomcat server, btw) to inspect those two headers and do the redirection, without having to write too much code myself. The Spring Boot (we're on version 1.4) docs state to use the following application properties:

security.require_ssl=true
server.tomcat.remote_ip_header=x-forwarded-for
server.tomcat.protocol_header=x-forwarded-proto

Testing this with Postman gives me an HTTP 200 where I should be getting a 301/302 redirect. I suspect this is due to me using Spring Security. To make it work with Spring Security, I could add this to my WebSecurityConfigurerAdapter :

 http.requiresChannel().anyRequest().requiresSecure();

But now my headers are ignored and all requests are forwarded to HTTPS, even if the original request was HTTPS already . So now nothing works.

The answer by Rodrigo Quesada here seems to be what I need, but it still doesn't respect my headers, for some reason. This request still gives me a redirect instead of a 200:

GET / HTTP/1.1
Host: localhost:8080
X-Forwarded-Proto: https
X-Forwarded-For: 192.168.0.30

There are many other solutions here and on other websites, using another intermediary web server, or configuring NGINX or Apache on AWS to do the redirection. But I'm using Tomcat already, why would I want to configure yet another web server.

So how can I actually configure Spring Boot/Tomcat/Spring Security in a Spring-way to do this redirection? Is there a way to extend the behavior of the requiresSecure() function so that it takes into account my request headers? Or even better, are there any Spring Security alternatives to the application properties I tried?

I fixed it for now with a bit of custom logic. A HandlerInterceptor can be registered to inspect the HttpServletRequest before it is passed to any REST controller. It looks like this:

private HandlerInterceptor protocolInterceptor() {
    return new HandlerInterceptorAdapter() {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
            String forwardedProtocol = request.getHeader("x-forwarded-proto");

            // x-forwarded-proto header is required, send 400 if it's missing
            if (forwardedProtocol == null) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "x-forwarded-proto header required from the load balancer");
                return false;
            }

            // Client request protocol must be https, send 301 if it's http
            if (!forwardedProtocol.equals("https")) {
                String host = request.getHeader("host");

                // Send error 400 if 'host' is empty
                if (host == null) {
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Host header missing");
                    return false;
                }

                response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
                response.setHeader("Location", "https://" + host + request.getServletPath());
                return false;
            }

            // All is well in all other cases, continue
            return true;
        }
    };
}

I check if the x-forwarded-proto header exists first. If it doesn't, I send a client error response. Then I check the header value, which must be https. HTTP 1.1 always has a host header, but just in case I check that for null values as well. Then finally I send a 301 permanent redirection. Note that I put my interceptor in a method, but you can just as well make a bean out of it.

Next we need to register the interceptor. If you don't have one yet, create a configuration class extending WebMvcConfigurerAdapter and override the addInterceptors method:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    logger.info("Registering custom interceptors");
    logger.debug("Registering protocol interceptor to redirect http to https");
    registry.addInterceptor(protocolInterceptor());
    logger.info("Registered custom interceptors");
}

Disable this configuration class during development/testing, or make sure to set the appropriate headers in all request from testing libraries like MockMVC or you will break those tests.

If I have some spare time, I'll try to get the out-of-the-box solution to work, but for now, this will (have to) do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM