简体   繁体   中英

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

Description

We're installing some application running Tomcat 6 behind Nginx for different clients. Some of those installations are HTTP only, some HTTPS only, somewhere both. One of those installations has HTTP and HTTPS working on non-standard ports (8070 and 8071) due to lack of public IPs. Application at hand is displayed as an iframe in another app.

Current behaviour

Tomcat redirects all HTTPS requests to HTTP (so nothing displayed in iframe due to browser restrictions for mixed content).

Current configuration

Iframe code:

<iframe src="/saiku-ui">

Tomcat's 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 vhost:

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;
}

Desired behaviour

  1. Tomcat should listen on one single port for both HTTP and HTTPS requests.

    If there will be two <Connector> tags it will be much harder to configure Nginx.

  2. Tomcat should not redirect between schemas.

  3. Nginx may listen on arbitrary ports (eg listen 8071 ssl spdy; ).
  4. Links, generated by Tomcat should be either relative or include schema, host, and port as provided by Nginx.

Additional info

I've tried to add schema and proxyPort attributes to <Connector> , after that Tomcat will always redirect from HTTP to HTTPS (at least it's better).

I can't google such a configuration and not experienced with Tomcat. Please help.

Actually what I want really is not possible, so it's required to have two separate Connector tags and two upstreams in Nginx, like so:

Tomcat's 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"
/>

Matching Nginx configuration:

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;
}

Please note that Tomcat receives plain HTTP traffic on both 8080 and 8443 ports (no SSL there, it's terminated by Nginx), but for connections on 8443 port it will generate links must start with https:// instead of http:// (via attributes scheme="https" secure="true" ) and will insert in links ports, specified in proxyPort attribute.

Nginx will terminate SSL and proxy all secure connections to the 8443 port of Tomcat via saiku-server-https upstream, where https is the value of $scheme Nginx request variable (see location block)

If you are using Spring and don't want to change the tomcat configuration, there is another solution based on this answer .

After a few hours of google, I found there is no standard solution like a officially supported configuration, refer this comment .

See the java doc 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");
    }
}

Filter

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() {
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM