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 "%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協議工做的流程有所瞭解,才能很容易的找到解決問題的思路。
若各位夥伴有更好的解決方案,也請分享或者一塊兒探討。