Apache 后面的 Tomcat 使用 ajp 进行 Spring Boot 应用程序

Posted

技术标签:

【中文标题】Apache 后面的 Tomcat 使用 ajp 进行 Spring Boot 应用程序【英文标题】:Tomcat behind Apache using ajp for Spring Boot application 【发布时间】:2015-01-20 04:26:37 【问题描述】:

我一直在尝试使用使用嵌入式 Tomcat 的 Spring Boot 应用程序配置 Apache Web 服务器。在 Spring Boot 之前,我曾经创建一个 ajp.conf 文件,例如:

<VirtualHost *:80>
   ServerName localhost
   <Proxy *>
      AddDefaultCharset Off
      Order deny,allow
      Allow from all
   </Proxy>

   ProxyPass /app ajp://localhost:8009/app
   ProxyPassReverse /app ajp://localhost:8009/app

 </VirtualHost>

并包含在 httpd.conf 文件中,如

Include /opt/lampp/apache2/conf/ajp.conf

并且在Tomcat的server.xml文件中,我曾经配置它监听8009端口

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" connectionTimeout="5000"

此设置有效。但是,现在使用 Spring Boot,我正在尝试使用嵌入式 tomcat 实现类似的功能。我在这里阅读了Spring Boot Documentation,并在我的 application.yml 文件中添加了以下属性:

server:
    port: 8080
    tomcat:
        remote_ip_header: x-forwarded-for
        protocol_header: x-forwarded-proto

我的 ajp.conf 文件如下所示:

<VirtualHost *:80>
   ServerName localhost
   <Proxy *>
      AddDefaultCharset Off
      Order deny,allow
      Allow from all
   </Proxy>

   ProxyPass /app ajp://localhost:8009/
   ProxyPassReverse /app ajp://localhost:8009/

 </VirtualHost>

我的spring boot tomcat配置类为

@Configuration
public class TomcatConfiguration 

private final Logger log = LoggerFactory.getLogger(TomcatConfiguration.class);

@Bean
public EmbeddedServletContainerFactory servletContainer() 
    TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
    tomcat.addAdditionalTomcatConnectors(createConnector());
    tomcat.addContextValves(createRemoteIpValves());
    return tomcat;


private RemoteIpValve createRemoteIpValves()
    RemoteIpValve remoteIpValve = new RemoteIpValve();
    remoteIpValve.setRemoteIpHeader("x-forwarded-for");
    remoteIpValve.setProtocolHeader("x-forwarded-protocol");
    return remoteIpValve;


private Connector createConnector() 
    Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
    Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
    connector.setScheme("ajp");
    connector.setProtocol("AJP/1.3");
    connector.setRedirectPort(8443);
    //connector.setSecure(true);
    connector.setPort(8009);
    return connector;

在我的 apache 错误日志中,我看到:

AH01080: ajp_msg_check_header() got bad signature 4854
[proxy_ajp:error] [pid 24073] AH01031: ajp_ilink_receive() received bad header
[proxy_ajp:error] ajp_read_header: ajp_ilink_receive failed
[proxy_ajp:error] (120007)APR does not understand this error code: [client xx.xx.xx.xx:60916] AH00878: read response failed from (null) (*)

不确定这里发生了什么。我在网上搜索了很多,但找不到关于如何使用 spring boot 应用程序在 apache 后面提供 tomcat 的好的文档。最后,我也想对多个 tomcat 实例进行负载平衡。

【问题讨论】:

docs.spring.io/autorepo/docs/spring-boot/1.0.0.RC4/api/org/… 或 docs.spring.io/spring-boot/docs/current/reference/html/… . 为什么要创建 HTTP NIO 连接器?您需要创建 AJP 连接器 - new Connector("AJP/1.3") @PavelHoral 你把我推向了正确的方向。当我使用 org.apache.coyote.ajp.AjpProtocol 连接器时,它起作用了。不确定,我如何做到这一点是最好的方法。但是,它现在有效。非常感谢! 【参考方案1】:

可通过属性或 yml 文件进行配置。

@Configuration
@ConfigurationProperties(prefix = "tomcat")
public class TomcatConfiguration 
   private int ajpPort = 8009;

   private boolean ajpAllowTrace = false;
   private boolean ajpSecure = false;
   private String ajpScheme = "http";
   private boolean ajpEnabled;


  @Bean
  public EmbeddedServletContainerFactory servletContainer() 

    TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
    if (isAjpEnabled()) 
        Connector ajpConnector = new Connector("AJP/1.3");
        ajpConnector.setProtocol("AJP/1.3");
        ajpConnector.setPort(getAjpPort());
        ajpConnector.setSecure(isAjpSecure());
        ajpConnector.setAllowTrace(isAjpAllowTrace());
        ajpConnector.setScheme(getAjpScheme());
        tomcat.addAdditionalTomcatConnectors(ajpConnector);
    

    return tomcat;
    
// ... Get/Set

application.yml

tomcat:
  ajpEnabled: true
  ajpPort: 9009
  ...

【讨论】:

【参考方案2】:

遇到了类似的问题,但使用的是 HTTP 代理。在对 Spring Boot 1.3 进行了一些调试后,我找到了以下解决方案。 AJP 代理应该类似。

1.您必须在 Apache 代理上设置标头:

<VirtualHost *:443>
    ServerName www.myapp.org
    ProxyPass / http://127.0.0.1:8080/
    RequestHeader set X-Forwarded-Proto https
    RequestHeader set X-Forwarded-Port 443
    ProxyPreserveHost On
    ... (SSL directives omitted for readability)
</VirtualHost>

2. 您必须告诉您的 Spring Boot 应用程序使用这些标头。所以把下面这行放在你的 application.properties 中(或者 Spring Boots 理解属性的任何其他地方):

server.use-forward-headers=true

如果您正确执行这两件事,您的应用程序发送的每个重定向都将不会转到http://127.0.0.1:8080/[path],而是自动转到https://www.myapp.com/[path]

更新 1。 关于此主题的文档是 here。您至少应该阅读它以了解属性 server.tomcat.internal-proxies,它定义了可信任的代理服务器的 IP 地址范围。

【讨论】:

不错的补充;这也是我所达到的。一切都很好,有一个小问题,当我使用 spring hateoas ControllerLinkBuilder.linkTo + methodOn 我生成的链接以 https 开头,但还包含端口号 443。当我使用 BasicLinkBuilder 时,端口不会添加到 URL .对此有什么想法吗? @tim 从未使用过这些课程。您可以尝试调试到 ContollerLinkBuilder 和 BasicLinkBuilder。如果他们有不同,您可以向 Spring 人员发布错误报告。他们反应很快。【参考方案3】:

从上面的cmets推导出来:

@Configuration
public class TomcatAjpConfig 

@Bean
@SuppressWarnings("static-method")
public EmbeddedServletContainerFactory servletContainer() 
    TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
    tomcat.addAdditionalTomcatConnectors(createConnector());
    tomcat.addContextValves(createRemoteIpValves());
    return tomcat;


private static RemoteIpValve createRemoteIpValves() 
    RemoteIpValve remoteIpValve = new RemoteIpValve();
    remoteIpValve.setRemoteIpHeader("x-forwarded-for");
    remoteIpValve.setProtocolHeader("x-forwarded-proto");
    return remoteIpValve;


private static Connector createConnector() 
    Connector connector = new Connector("AJP/1.3");
    connector.setPort(8009);
    return connector;



【讨论】:

以上是关于Apache 后面的 Tomcat 使用 ajp 进行 Spring Boot 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

Apache + Tomcat:使用 mod_proxy 代替 AJP

Apache 2.4 代理 AJP 使用 Tomcat 8 服务多个域

(CVE-2020-1938)Apache Tomcat AJP文件包含漏洞复现

apache tomcat ajp mod jk

辅助 ajp 工作者不在 apache 和 tomcat 之间工作

Apache-Tomcat-Ajp漏洞(CVE-2020-1938)漏洞复现