在反向代理后使用Spring Security要求HTTPS

17

我有一个使用Spring Security保护的Spring MVC应用程序。大部分应用程序使用简单的HTTP来保存资源,但是一小部分处理更加机密的信息并且需要HTTPS通道。

security-config.xml 中提取:

<sec:http authentication-manager-ref="authenticationManager" ... >
    ...
    <sec:intercept-url pattern="/sec/**" requires-channel="https"/>
    <sec:intercept-url pattern="/**" requires-channel="http"/>
</sec:http>

一切都正常,直到我们决定将应用迁移到主服务器上,在那里应用服务器在反向代理后运行。由于现在HTTPS由反向代理处理,因此应用服务器仅看到HTTP请求,并禁止访问/sec/**层次结构。

经过一些研究,我发现代理会添加一个X-Forwarded-Proto: https头部(*),但是在Spring Security中使用HttpServletRequest.isSecure()来确定提供的通道安全性(摘自SecureChannelProcessor javadoc)。

我该如何告诉Spring Security一个X-Forwarded-Proto: https头部就足以表示安全请求呢?

我知道我可以报告代理配置中的这一部分,但代理管理员真的不喜欢这个解决方案,因为有很多应用程序位于代理后面,配置可能变得无法管理。

我目前正在使用带有XML配置的Spring Security 3.2,但我准备接受基于Java配置和/或更高版本的答案。

(*) 当然,如果该头部存在于传入请求中,代理会将其删除,因此应用程序可以确信它。


你的应用服务器是什么?Tomcat连接器中没有此设置吗? - Neil McGuigan
@NeilMcGuigan:是的,它是Tomcat。难道问题会这么简单吗? - Serge Ballesta
@NeilMcGuigan:请将其写成答案,以便我可以接受它。 - Serge Ballesta
3个回答

22

这是NeilMcGuigan回答的一个后续问题,表明解决方案在Servlet容器端实现。

Tomcat更好。它有一个专门用于掩盖反向代理副作用的阀门。来自Tomcat文档的提取:Remote IP Valve

该阀门的另一个功能是使用请求头(例如,“X-Forwarded-Proto”)中的代理或负载均衡器提供的协议替换外观方案(http/https)、服务器端口和request.secure。

以下是该阀门配置的示例:

<Valve className="org.apache.catalina.valves.RemoteIpValve"
    internalProxies="192\.168\.0\.10|192\.168\.0\.11"
    remoteIpHeader="x-forwarded-for" proxiesHeader="x-forwarded-by"
    protocolHeader="x-forwarded-proto" />

这样,在不进行应用程序本身的其他配置的情况下,调用Request.isSecure()将返回true,如果请求包含一个X-Forwarded-Proto=https的头字段。

我想到了另外两种可能性,但明确更喜欢这一种:

  • 使用一个过滤器,在Spring Security ChannelProcessingFilter之前激活,用一个HttpServletRequestWrapper覆盖isSecure()来处理X-Forwarded-Proto头 - 需要编写和测试过滤器和包装器
  • 使用一个Spring BeanPostProcessor查找ChannelProcessingFilter并手动注入一个能考虑X-Forwarded-Proto头的ChannelDecisionManager - 真的太低级了

1
我认为这应该是被选中的答案。在我看来,绝对是最好的解决方案。谢谢! - Giordano
3
当我提出问题时,帮助我找到答案的是尼尔的回答。这就是我接受他的原因 :-) 但是,由于它并不如我所希望的那样完整,我添加了这个回答。 - Serge Ballesta
非常有道理,谢谢你分享这个额外的信息 ^^ - Giordano
如果您运行双栈IPv4 / IPv6,则必须将对应于两个堆栈的模式添加到internalProxies中。我曾因此受挫,因为Tomcat版本中的默认值不包括IPv6。 - revau.lt
嗨@SergeBallesta,如果我正在使用IBM WebSphere,您是否偶然知道解决方案?我在LB后面,其中SSL / TLS终止,并且LB卸载http到WS(WebServer),然后将请求转发到AP(AppServer)。我的AppServer,Spring Security,期望https,但由于它是http,因此我会得到302,因为Spring尝试将其重定向到https,显然找不到。 - jumping_monkey

15

使用Spring Boot(至少在嵌入的Tomcat中)可以轻松实现此目的。

1. 将以下行添加到您的application.properties文件中:

server.forward-headers-strategy=native
server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.protocol-header=x-forwarded-proto

2. 用以下方法对你的HttpSecurity配置进行处理。

// final HttpSecurity http = ...
// Probably it will be in your `WebSecurityConfigurerAdapter.configure()`

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

这篇文章来源于Spring Boot参考指南

84.3在代理服务器后运行时启用HTTPS

请同时查看下方的答案,了解与Spring Boot 2.2相关的具体内容:如何处理Spring Boot 2.2中的X-Forwarded headers问题


1
是否可以配置此重定向使用301永久而不是302临时? - Niemi
我没有确凿的答案,但既然它来自Tomcat,我会检查类似这样的东西:https://dev59.com/11wY5IYBdhLWcg3wQ16k - Roman Nikitchenko
2
也适用于嵌入式Jetty。我在Internet-facing AWS ALB HTTPS中使用了它。 - fdaugan
嗨@RomanNikitchenko,你是否知道如果我使用IBM WebSphere的解决方案?我在LB后面,SSL/TLS终止,并且LB卸载http到WS(WebServer),然后将请求转发到AP(AppServer)。我的AppServer,Spring Security,期望https,但由于它是http,我得到302,因为Spring试图将其重定向到https,显然找不到。 - jumping_monkey
@jumping_monkey 我会检查一下。理论上应该可以工作 - 我使用不同的负载均衡器,包括Cloud Foundry路由器、HAProxy和HAProxy + K8s ingress - 它们都可以正确路由。 Spring Boot应该接受它将接收HTTP。 - Roman Nikitchenko

8
如果您的网站是HTTPS,并且在另一个处理TLS终止的系统后面运行Apache Tomcat,您可以告诉Tomcat“假装”它正在处理TLS终止。
这将使request.isSecure()返回true
要实现这一点,您需要在server.xml中的Connector配置中添加secure="true"
请参见https://tomcat.apache.org/tomcat-7.0-doc/config/http.html
还请留意scheme属性。

1
这不完全是预期的答案,但它引导我走向了正确的方向:很高兴接受它。 - Serge Ballesta
@SergeBallesta,你是怎么解决的?我已经尝试了所有可能的方法让它正常工作。你到底是用了什么方法使其正常运行的? - Hemant Nagpal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接