LB+nginx+tomcat7集群模式下的https请求重定向(redirect)后变成http的解决方案

0. 环境信息css

Linux:Linux i-8emt1zr1 2.6.32-573.el6.x86_64 #1 SMP Wed Jul 1 18:23:37 EDT 2015 x86_64 x86_64 x86_64 GNU/Linuxnginx

nginx:nginx version: openresty/1.9.3.2web

Tomcat:Server version: Apache Tomcat/7.0.64apache

 

1. 问题描述
咱们开发的客服系统,由于消息的到来,有的谷歌浏览器(V62)不支持http的消息提醒,要求https,故而,咱们的系统,要将系统改形成https模式,另外,咱们的系统,也有必要转化为https,为后续推广作准备。浏览器

 

2. 系统架构
LB+nginx+tomcat集群tomcat

 

3. 当前配置状况
SSL证书配置在LB上,nginx和tomcat服务器上,任然采用http协议通信。即LB在接收到客户浏览器https请求消息后,将转发给LB下挂载的nginx上,都是以http的方式转发,nginx对这些请求进行反向代理,代理到后面的tomcat服务器上。安全

 

4. 遇到的问题
客服系统,有权限控制,基于tomcat的web应用,用户登陆后,执行redirect跳转到指定的服务页面。就是这个跳转,遇到了问题,redirect在这里都被当作http跳转了服务器

登陆前的样子:websocket

登陆后的样子:架构

问题主要发生在Tomcat7上,验证过tomcat8,是不存在问题的。

 

5. 如何解决

针对Tomcat7的这个问题,思路很简单,重点是解决redirect的时候,通知客户端浏览器以正确的scheme(https仍是http)进行再次发起请求。
问题是, nginx这个时候收到的请求是来自LB的http请求了,怎么弄?实际上是有办法的,能够利用HttpRequest中的referer字段,这个字段的含义,自行科普吧。将referer的请求scheme信息,用来做为当前请求的scheme,如此能够保证全部的请求都是同一个scheme,不会由于redirect而遗漏信息。

nginx里面相应的配置以下:

     location /CSS/websocket {
            proxy_pass http://css_ws_svr;
            proxy_set_header Host $host;
            proxy_set_header Remote_Addr $remote_addr;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

        location /CSS {
            proxy_pass http://css_svr;
            proxy_set_header Host $host;
            proxy_set_header Remote_Addr $remote_addr;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            set $mscheme $scheme; if ($http_referer ~* ^https.*) { set $mscheme "https"; }
            proxy_set_header X-Forwarded-Proto $mscheme;
        }

如上配置,通过nginx反向代理后的HttpServletRequest中header部分就带上了字段X-Forwarded-Proto。

 

另一方面,就是tomcat里面,要作一个配置,让tomcat在解析请求和作重定向的时候,知道用什么协议。主要的配置在server.xml里面的Engine下,定义一个Value元素。

具体配置以下:

<Engine name="Catalina" defaultHost="localhost">

      
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

        <Valve className="org.apache.catalina.valves.RemoteIpValve" remoteIpHeader="X-Forwarded-For" protocolHeader="X-Forwarded-Proto" protocolHeaderHttpsValue="https"/>

        <Context path="/CSS" docBase="/home/tomcat/app/cssv2"/>
      </Host>
    </Engine>

这个配置里面,重点是protocolHeader字段,意思就是说,当protocolHeader字段的值为protocolHeaderHttpsValue的https的时候,认为是安全链接,不然就是http的非安全链接。
对应的代码逻辑,能够看org.apache.catalina.valves.RemoteIpValve这个类的源码

public void invoke(org.apache.catalina.connector.Request request, Response response)
 throws IOException, ServletException
{
......
if (protocolHeader != null) {
    String protocolHeaderValue = request.getHeader(protocolHeader);
    if (protocolHeaderValue != null)
    {

        if (protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue)) {
            request.setSecure(true);
            
            request.getCoyoteRequest().scheme().setString("https");
        
            setPorts(request, httpsServerPort);
        } else {
            request.setSecure(false);
                
            request.getCoyoteRequest().scheme().setString("http");
                
            setPorts(request, httpServerPort);
        }
    }
}
......
}

 

通过上面的分析和配置修改,最终很灵活的实现https和http同时工做。搞定这个问题,重点仍是要对Http协议工做的流程有所了解,才能很容易的找到解决问题的思路。

若各位伙伴有更好的解决方案,也请分享或者一块儿探讨。

相关文章
相关标签/搜索