LB+nginx+tomcat7集群模式下的https请求重定向(redirect)后变成http的解决方案
Posted on 2018-05-16 19:15 shihuc 阅读(7242) 评论(1) 编辑 收藏 举报0. 环境信息
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/Linux
nginx:nginx version: openresty/1.9.3.2
Tomcat:Server version: Apache Tomcat/7.0.64
1. 问题描述
我们开发的客服系统,因为消息的到来,有的谷歌浏览器(V62)不支持http的消息提醒,要求https,故而,我们的系统,要将系统改造成https模式,另外,我们的系统,也有必要转化为https,为后续推广做准备。
2. 系统架构
LB+nginx+tomcat集群
3. 当前配置情况
SSL证书配置在LB上,nginx和tomcat服务器上,任然采用http协议通讯。即LB在接收到客户浏览器https请求消息后,将转发给LB下挂载的nginx上,都是以http的方式转发,nginx对这些请求进行反向代理,代理到后面的tomcat服务器上。
4. 遇到的问题
客服系统,有权限控制,基于tomcat的web应用,用户登录后,执行redirect跳转到指定的服务页面。就是这个跳转,遇到了问题,redirect在这里都被当做http跳转了。
登录前的样子:
登录后的样子:
问题主要发生在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 "%r" %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协议工作的流程有所了解,才能很容易的找到解决问题的思路。
若各位伙伴有更好的解决方案,也请分享或者一起探讨。