Tomcat Nginx 背后:如何在非标准端口上同时代理 HTTP 和 HTTPS?

Tomcat behind Nginx: how to proxy both HTTP and HTTPS, possibly on non-standard ports?

描述

我们正在为不同的客户端在 Nginx 后面安装一些应用程序 运行 Tomcat 6。其中一些安装仅支持 HTTP,一些仅支持 HTTPS,有些则两者兼而有之。由于缺少 public IP,其中一个安装的 HTTP 和 HTTPS 在非标准端口(8070 和 8071)上工作。手头的应用程序在另一个应用程序中显示为 iframe。

当前行为

Tomcat 将所有 HTTPS 请求重定向到 HTTP(因此由于浏览器对混合内容的限制,iframe 中不显示任何内容)。

当前配置

iframe 代码:

<iframe src="/saiku-ui">

Tomcat的server.xml:

<Connector port="8080" protocol="HTTP/1.1"/>
<!-- A bit later... -->
<Valve className="org.apache.catalina.valves.RemoteIpValve"
      remoteIpHeader="x-forwarded-for"
      protocolHeader="x-forwarded-proto"
    />

Nginx 虚拟主机:

server {
  listen 80;
  listen 443 ssl spdy;

  location /saiku-ui {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://saiku-server; # This is upstream name
    proxy_redirect off;
  }
}

upstream saiku-server {
  server ip.of.tomcat.server:8080;
}

期望的行为

  1. Tomcat 应该在一个端口上侦听 HTTP 和 HTTPS 请求。

    如果有两个 <Connector> 标签,配置 Nginx 会更难。

  2. Tomcat 不应在模式之间重定向。

  3. Nginx 可以监听任意端口(例如 listen 8071 ssl spdy;)。
  4. 由 Tomcat 生成的链接应该是相对的,或者包括 Nginx 提供的架构、主机和端口。

附加信息

我尝试将 schemaproxyPort 属性添加到 <Connector>,之后 Tomcat 将始终从 HTTP 重定向到 HTTPS(至少更好) .

我不能google这样的配置,也没有使用过Tomcat。请帮忙

实际上我想要的是不可能的,所以需要在Nginx中有两个独立的Connector标签和两个上游,像这样:

Tomcat的server.xml:

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           proxyPort="80"
/>

<Connector port="8443" protocol="HTTP/1.1"
           connectionTimeout="20000"
           proxyPort="443"
           scheme="https" secure="true"
/>

匹配的 Nginx 配置:

server {
  listen 80;
  listen 443 ssl spdy;

  location /saiku-ui {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://saiku-server-$scheme; # This is upstream name, note the variable $scheme in it
    proxy_redirect off;
  }
}

upstream saiku-server-http {
  server     ip.of.tomcat.server:8080;
}

upstream saiku-server-https {
  server     ip.of.tomcat.server:8443;
}

请注意,Tomcat 在 8080 和 8443 端口上接收纯 HTTP 流量(那里没有 SSL,它被 Nginx 终止),但是对于 8443 端口上的连接,它将生成必须以 [=14 开头的链接=] 而不是 http://(通过属性 scheme="https" secure="true")并将插入链接端口,在 proxyPort 属性中指定。

Nginx 将终止 SSL 并通过 saiku-server-https 上游代理到 Tomcat 的 8443 端口的所有安全连接,其中 https$scheme Nginx 请求变量的值(参见 location 版块)

如果您正在使用 Spring 并且不想更改 tomcat 配置,还有另一种基于 this answer.

的解决方案

几个小时后google,发现没有像官方支持配置这样的标准方案,参考

请参阅 java 文档 HttpServletResponse.sendRedirect

Sends a temporary redirect response to the client using the specified redirect location URL. This method can accept relative URLs; the servlet container must convert the relative URL to an absolute URL before sending the response to the client. If the location is relative without a leading '/' the container interprets it as relative to the current request URI. If the location is relative with a leading '/' the container interprets it as relative to the servlet container root.

HttpServletResponseWrapper

import org.apache.commons.lang.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;

public class SendRedirectOverloadedResponseBasedOnXForwardedProtocol extends HttpServletResponseWrapper {

    private HttpServletRequest request;

    public SendRedirectOverloadedResponseBasedOnXForwardedProtocol(HttpServletRequest request,
                                                                   HttpServletResponse response) {
        super(response);
        this.request = request;
    }

    public void sendRedirect(String location) throws IOException {

        String xForwardedProtocol = request.getHeader("X-Forwarded-Protocol");
        String host = request.getHeader("Host");
        if (StringUtils.isNotBlank(xForwardedProtocol) && StringUtils.isNotBlank(host) && !isUrlAbsolute(location)) {
            location = xForwardedProtocol + "://" + host + location;
        }
        super.sendRedirect(location);
    }

    public boolean isUrlAbsolute(String location) {
        location = location == null ? "" : location;
        return location.toLowerCase().startsWith("http");
    }
}

过滤器

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class SendRedirectBasedOnXForwardedProtocolFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, new SendRedirectOverloadedResponseBasedOnXForwardedProtocol((HttpServletRequest) request, (HttpServletResponse) response));
    }

    @Override
    public void destroy() {
    }
}