带有负载均衡器后面的Spring Security的Spring Boot:将HTTP重定向到HTTPS

时间:2018-07-03 09:55:19

标签: java amazon-web-services spring-boot tomcat spring-security

我正在使用Elastic Beanstalk部署Spring Boot Web服务器,该服务器在应用程序负载平衡器(Elastic Load Balancer)后面运行。一个业务规则是仅应通过HTTPS与该Web服务器联系。因此,任何HTTP请求都必须先转发到HTTPS。

基于Amazon的this文章,我应该简单地检查由负载均衡器设置的x-forwarded-forx-forwarded-proto标头。这些标头包含有关客户端向负载均衡器发出的原始请求的信息。

因此,我开始寻找Spring Boot的内置方法(我正在使用内置的Tomcat服务器,btw)来检查这两个标头并进行重定向,而不必自己编写太多代码。 Spring Boot(我们的版本为1.4)docs状态为使用以下应用程序属性:

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

使用Postman进行测试可以给我一个HTTP 200,在这里我应该获得301/302重定向。我怀疑这是由于我使用Spring Security。为了使其与Spring Security一起使用,我可以将其添加到我的WebSecurityConfigurerAdapter

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

但是现在我的标头被忽略,并且所有请求都转发到HTTPS,即使原始请求已经是HTTPS 。所以现在什么都行不通了。

Rodrigo Quesada here的答案似乎是我所需要的,但是由于某种原因,它仍然不尊重我的标题。该请求仍然给我重定向,而不是200:

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

在这里和其他网站上,还有许多其他解决方案,使用其他中间Web服务器,或者在AWS上配置NGINX或Apache来进行重定向。但是我已经在使用Tomcat,为什么还要配置另一个 Web服务器。

那么我该如何真正地以Spring方式配置Spring Boot / Tomcat / Spring Security来进行重定向?有没有一种方法可以扩展requiresSecure()函数的行为,以便它考虑我的请求标头?甚至更好的是,我尝试过的应用程序属性是否有Spring Security替代品?

1 个答案:

答案 0 :(得分:0)

我现在通过一些自定义逻辑对其进行了修复。可以注册HandlerInterceptor来检查HttpServletRequest,然后再将其传递给任何REST控制器。看起来像这样:

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

我检查x-forwarded-proto头是否首先存在。如果没有,我将发送客户端错误响应。然后,我检查标头值,该值必须为https。 HTTP 1.1始终具有一个host头,但以防万一我还要检查是否为空值。最后,我发送了301永久重定向。请注意,我将拦截器放在了一个方法中,但是您也可以用它来制作一个bean。

接下来,我们需要注册拦截器。如果还没有,请创建一个扩展WebMvcConfigurerAdapter的配置类并覆盖addInterceptors方法:

@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");
}

在开发/测试期间禁用此配置类,或确保在来自测试库(例如MockMVC)的所有请求中设置适当的标头,否则您将破坏这些测试。

如果我有空闲时间,我将尝试使开箱即用的解决方案起作用,但是就目前而言,这将(必须)做到。