Enforce HTTPS with Embedded Jetty on Heroku

2019-06-05 03:01发布

问题:

We have a Java servlet, running with embedded Jetty, deployed to Heroku using the "SSL Endpoint" add-on. I have the requirement that all pages in the app be served over HTTPS, regardless of whether the user navigates to our app with http or https.

The application recognizes requests made over https, but it also accepts http requests without redirecting (which it should not do). Also, if the user starts with an https connection, then whenever a form is posted and redirected to a "GET" request, any https connection is reverted to http.

I tried adding a URL filter that simply changed "http://" to "https://" and do a redirect using the following code (imports removed for brevity):

public class UrlFilter implements Filter
{
  protected FilterConfig filterConfig;

  @Override
  public void destroy(){}

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
  {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    String incomingUrl = httpRequest.getRequestURL().toString();
    if ( (!httpRequest.isSecure()) && incomingUrl.contains(".webapps.stjude.org")  )
    {
      String newUrl = incomingUrl.replace("http://", "https://");
      httpResponse.sendRedirect(newUrl);
    }
    chain.doFilter(request, response);
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException
  {
    this.filterConfig = filterConfig;
  }
}

Then, I added this to my web.xml file:

<filter>
  <filter-name>urlFilter</filter-name>
  <filter-class>org.stjude.radio.ui.filters.UrlFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>urlFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

The problem with this approach is that the app returns a "too many redirects" error (redirect loop).

I'm sure this question has been addressed, but I can't for the life of me find exactly what I need to do to make this work.

BTW, I tried adding the following to web.xml also, but this simply caused requests to fail.

<security-constraint>
  <web-resource-collection>
    <web-resource-name>SSL Pages</web-resource-name>
    <url-pattern>/*</url-pattern>
    <http-method>GET</http-method>
    <http-method>PUT</http-method>
    <http-method>POST</http-method>
  </web-resource-collection>
  <user-data-constraint>
    <transport-guarantee>CONFIDENTIAL</transport-guarantee>
  </user-data-constraint>
</security-constraint>

Any help would be greatly appreciated.

回答1:

You need to check the x-forwarded-proto header to see if the request was secure:

Boolean secure = false;
if (request.headers.get("x-forwarded-proto") != null) {
  secure = request.headers.get("x-forwarded-proto").values.contains("https");
}
System.out.println("secure = " + secure);


回答2:

I have checked with Heroku (maybe I miss here a version, and buildpack ,and so on ) but definitely you got none of this information:

  • a X-Forwarded-Proto point to http
  • a HttpServletRequest#scheme returns a http
  • a HttpServletRequest#secure return false ....