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

Posted

技术标签:

【中文标题】负载均衡器后面带有 Spring Security 的 Spring Boot:将 HTTP 重定向到 HTTPS【英文标题】:Spring Boot with Spring Security behind Load Balancer: redirect HTTP to HTTPS 【发布时间】:2018-12-11 14:14:35 【问题描述】:

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

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

所以我开始寻找 Spring Boot 的内置方法(我正在使用内置的 Tomcat 服务器,顺便说一句)来检查这两个标头并进行重定向,而不必自己编写太多代码。 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 替代方案?

【问题讨论】:

关于使用 Postman 进行测试。请注意,即使在发生重定向的情况下,邮递员也会返回最终状态代码。据我所知,重定向发生在幕后,因此重定向可能会起作用。用浏览器试试看当你访问http而不是https时会发生什么 我认为可能是这样。我也一直在使用 telnet 进行测试以获取确切的返回码。在那里我可以验证它是否总是导致重定向,忽略我的标题,或者永远不会重定向,再次忽略我的标题,这取决于我尝试的解决方案。 您希望在生产环境中配置 Nginx/Apache,因为如果没有安全证书,您将如何在本地进行调试?相关:***.com/questions/53289394/… 嘿@delucasvb,如果业务规则不存在会简单得多,对吧?只是出于好奇,为什么这个流程“HTTPS => ELB => offload HTTPS to HTTP => WS”不够好?行业惯例,性能提升了,ELB和WS之间也不需要加密了吧? 【参考方案1】:

我现在用一些自定义逻辑修复了它。可以注册 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 等测试库的所有请求中设置适当的标头,否则您将破坏这些测试。

如果我有空闲时间,我会尝试让开箱即用的解决方案发挥作用,但现在,这将(必须)这样做。

【讨论】:

以上是关于负载均衡器后面带有 Spring Security 的 Spring Boot:将 HTTP 重定向到 HTTPS的主要内容,如果未能解决你的问题,请参考以下文章

Grails Spring Security 使用 https 重定向到登录页面

如何将 Spring Security 与负载均衡器一起使用?

带有 https 的负载均衡器后面的 Keycloak Docker 失败

Spring 云负载均衡器 - 带有健康检查/重试的 Feign + SimpleDiscoveryClient

在 Google 前端负载均衡器后面运行时如何正确从 Spring Boot 2.3 重定向到 HTTPS

Spring Cloud中常见负载均衡实现技术