Apache反向代理:本地同端口多虚拟主机代理异常排查与解决
问题
在本地Docker容器部署了HTTPD Apache反向代理,对接OpenID Connect SSO认证,需要配置3个不同的SSO,因此在httpd.conf中配置了3个VirtualHost,期望第一个VirtualHost根据子路径转发到另外两个。但实际访问/api/sso2/login或/api/sso3/login时,均转发到第二个VirtualHost(调整VirtualHost顺序则都转到第三个)。以下是配置及/etc/hosts内容,求问题原因及正确配置方法。
现有配置
<VirtualHost *:80> ServerName ${MON_IP_HOST} OIDCProviderMetadataURL http://SSO1……. <Location /> #public </Location> <Location /api/SSO1/login> AuthType openid-connect Require valid-user </Location> ProxyPreserveHost On ProxyPass /api/sso2/login http://internal_sso2/api/sso2/login ProxyPassReverse /api/sso2/login http://internal_sso2/api/sso2/login ProxyPass /api/sso3/login http://sso3_local/api/sso3/login ProxyPassReverse /api/sso3/login http://sso3_local/api/sso3/login #--- main proxy pass --- ProxyPass / ${banckendapiurl} ProxyPassReverse / ${banckendapiurl} </VirtualHost> <VirtualHost internal_sso2:80> ServerName internal_sso2 OIDCProviderMetadataURL http://SSO2 …… <Location /> AuthType openid-connect Require valid-user </Location> ProxyPreserveHost On ProxyPass / ${banckendapiurl} ProxyPassReverse / ${banckendapiurl} </VirtualHost> <VirtualHost sso3_local:80> ServerName sso3_local OIDCProviderMetadataURL http://SSO3…. <Location /> AuthType openid-connect Require valid-user </Location> ProxyPreserveHost On ProxyPass / ${banckendapiurl} ProxyPassReverse / ${banckendapiurl} </VirtualHost>
/etc/hosts内容
127.0.0.1 localhost sso3_local internal_sso2
问题分析与解决方案
问题根源
ProxyPreserveHost On的误用:第一个VirtualHost中开启该配置后,代理请求会把原始Host头(即${MON_IP_HOST})传递给后端虚拟主机。而Apache虚拟主机匹配逻辑是:当请求Host头与虚拟主机ServerName不匹配时,默认使用配置文件中第一个匹配端口的虚拟主机,这就是调整顺序后跳转目标变化的原因。- 内部虚拟主机监听范围限制:内部两个虚拟主机仅监听
internal_sso2:80和sso3_local:80,但代理请求的Host头被篡改后,无法触发对应虚拟主机的匹配规则,只能 fallback 到第一个匹配端口的虚拟主机。
正确配置方法
方案1:针对内部代理请求关闭ProxyPreserveHost
保留内部虚拟主机的监听配置,仅在第一个虚拟主机中针对sso2、sso3的代理请求关闭ProxyPreserveHost,让后端虚拟主机能通过正确的Host头(internal_sso2/sso3_local)匹配到对应配置:
<VirtualHost *:80> ServerName ${MON_IP_HOST} OIDCProviderMetadataURL http://SSO1……. <Location /> #public </Location> <Location /api/SSO1/login> AuthType openid-connect Require valid-user </Location> # 针对sso2代理关闭PreserveHost <Proxy http://internal_sso2/api/sso2/login> ProxyPreserveHost Off </Proxy> ProxyPass /api/sso2/login http://internal_sso2/api/sso2/login ProxyPassReverse /api/sso2/login http://internal_sso2/api/sso2/login # 针对sso3代理关闭PreserveHost <Proxy http://sso3_local/api/sso3/login> ProxyPreserveHost Off </Proxy> ProxyPass /api/sso3/login http://sso3_local/api/sso3/login ProxyPassReverse /api/sso3/login http://sso3_local/api/sso3/login #--- main proxy pass --- ProxyPreserveHost On ProxyPass / ${banckendapiurl} ProxyPassReverse / ${banckendapiurl} </VirtualHost>
方案2:统一内部虚拟主机监听范围,通过ServerName精准匹配
将内部两个虚拟主机的监听地址改为*:80,让Apache通过Host头匹配对应虚拟主机,同时在代理时显式设置Host头:
修改内部虚拟主机配置
<VirtualHost *:80> ServerName internal_sso2 OIDCProviderMetadataURL http://SSO2 …… <Location /> AuthType openid-connect Require valid-user </Location> ProxyPreserveHost On ProxyPass / ${banckendapiurl} ProxyPassReverse / ${banckendapiurl} </VirtualHost> <VirtualHost *:80> ServerName sso3_local OIDCProviderMetadataURL http://SSO3…. <Location /> AuthType openid-connect Require valid-user </Location> ProxyPreserveHost On ProxyPass / ${banckendapiurl} ProxyPassReverse / ${banckendapiurl} </VirtualHost>
修改第一个虚拟主机的代理配置
# 针对sso2的代理,显式设置Host头 ProxyPass /api/sso2/login http://internal_sso2/api/sso2/login ProxyPassReverse /api/sso2/login http://internal_sso2/api/sso2/login RequestHeader set Host "internal_sso2" # 针对sso3的代理,显式设置Host头 ProxyPass /api/sso3/login http://sso3_local/api/sso3/login ProxyPassReverse /api/sso3/login http://sso3_local/api/sso3/login RequestHeader set Host "sso3_local" #--- main proxy pass --- ProxyPreserveHost On ProxyPass / ${banckendapiurl} ProxyPassReverse / ${banckendapiurl}
额外注意事项
- 确保Apache已启用
mod_proxy、mod_proxy_http、mod_headers(使用RequestHeader时)和mod_auth_openidc模块。 - 测试时可通过
curl -v http://${MON_IP_HOST}/api/sso2/login查看请求头和跳转过程,确认Host头是否正确传递。
内容的提问来源于stack exchange,提问作者ThierryC
相关产品推荐
相关产品推荐

