雙網隔離環境下CAS單點登錄的解決方案


在單位內架設的Web系統,如果使用CAS作為單點登錄方案,往往會遇到從單位的不同網絡(例如雙網隔離下的外網和內網)訪問時,系統無法正常登錄使用的問題。基於本人實踐,本文介紹一些解決方案。

技術背景

對CAS很熟悉的朋友可以跳過本章。
  用Java開發一個支持單點登錄SSO的Web應用,一般都需要部署兩個服務:CAS服務和Web應用服務。CAS的相關資料網上很多,例如:https://www.jianshu.com/p/d443cfc10646。這里只簡單敘述一下其工作方式,為解決方案做鋪墊。

1、開發和部署CAS服務和Web應用服務。假設:

CAS服務地址:http://192.168.1.68:8585/cas
Web1應用地址:http://192.168.1.50:8083/poweroa  tomcat
Web2應用地址:http://192.168.1.55:9010/ resin --沒有統一后綴(login、wui及其他)

2、在Web應用服務的配置文件中配置CAS服務器的地址。

3、用戶通過瀏覽器打開Web應用地址http://192.168.1.50:8083/poweroa時,Web應用發現用戶未登錄過(沒有session和ticket),於是從配置文件中讀取到CAS服務地址,然后把用戶當前請求地址附加在這個地址后面,告訴用戶的瀏覽器去跳轉到這個地址,形如 http://192.168.1.68:8585/cas/login?service=http://192.168.1.50:8083/poweroa。

4、瀏覽器被重定向到上述CAS服務地址后,出現CAS的登錄頁面,用戶輸入賬號登錄成功后,CAS服務端從瀏覽器請求地址中提取出用戶原來要訪問的http://192.168.1.50:8083/poweroa這個地址,附加上ticket后讓瀏覽器重定向到該地址。

5、瀏覽器被重定向到Web應用地址:http://192.168.1.50:8083/poweroa,Web應用服務端收到用戶請求,並從請求中獲取到sessionid、ticket等信息,然后在后台去訪問http://192.168.1.68:8585/cas,驗證ticket通過后即響應瀏覽器請求,通知瀏覽器重定向到Web應用首頁。整個登錄過程結束。

問題描述:

當系統只在一個獨立的網絡中運行時,CAS登錄過程沒有任何問題。但如果企事業單位采用了網絡隔離模式,典型地,把網絡分成內網和外網兩部分,服務器部署在內網,通過端口映射的方式把服務端口向外網開放,外網用戶只能通過外網IP訪問單位網絡。此時外網用戶將無法完成單點登錄過程。原因如下:
  假設單位的外網地址是113.120.95.60,並且已經成功地做了如下端口映射:

CAS服務外網映射:113.120.95.60:81 -> 192.168.1.68:8585
Web服務外網映射:113.120.95.50:80 -> 192.168.1.68:8083

當一個外網用戶輸入 http://113.120.95.50:80/poweroa時,端口映射能夠成功將請求轉給內網的http://192.168.1.50:8083/poweroa,而Web應用的配置文件中登記的CAS服務地址是內網地址http://192.168.1.68:8585/cas,因此Web應用會通知瀏覽器重定向該地址。然而瀏覽器是處在外網網絡中,當然無法連上該內網地址,於是瀏覽器報錯,登錄過程中斷。作為用戶,會看到瀏覽器地址欄顯示一個內網URL。

問題分析

出現登錄問題的本質原因是:
  CAS登錄過程依賴對CAS服務地址的重定向,而這個地址是在部署系統時固定配置好的,唯一的。如果內外網不能同時訪問該地址,則必定無法登錄成功。 上例中,外網用戶無法訪問http://192.168.1.2:8080/sso這個地址。
  要解決該問題,思路就應該是:用什么方法能夠使得***在重定向跳轉過程中,始終都使用當前用戶可以訪問***的地址。

解決方案

解決方案1:通過統一域名和DNS解決

       這是最簡單優雅的解決方案。
  簡單說來,上面的例子中因為使用了IP地址作為服務地址,從而造成內外網無法同時訪問的結果,那么把IP地址改成域名,通過內外網不同的DNS域名服務器設置,讓無論內網還是外網都能通過域名訪問到真正的服務地址,此問題就迎刃而解。具體做法如下:

1、給SSO服務和Web服務申請域名:

SSO服務域名:sso.mycompany.net
OA的Web應用域名:web.mycompany.net

2、在Web應用服務的配置文件中,配置CAS服務的地址為:

http://sso.mycompany.net:8080/cas。

3、DNS配置:

  • 在內網的DNS服務器上,把這兩個域名分別映射到192.168.1.68和192.168.1.50;
  • 在外網申請兩個IP地址IP1IP2(滿足特定前提下可以只用一個,參見后文),並分別在外網的DNS服務器分別綁定兩個域名到這兩個IP地址;

4、端口映射:分別把IP1IP2的8080端口映射到內網的192.168.1.68和192.168.1.50的8080端口;

5、告訴內外網所有用戶,應用的訪問地址是http://web.mycompany.net:8080/web

上述工作完成后,無論內網用戶還是外網用戶,當訪問http://web.mycompany.net:8080/web時,瀏覽器都會被重定向到http://sso.mycompany.net:8080/cas來進行單點登錄,而這個地址都會根據所在網絡不同,解析並導向到最終的內網服務器上,從而實現成功登錄。
  該方案的一個小小缺陷是因為地址必須相同,所以內外網必須使用相同的服務端口號,即如果內網服務使用了8080,外網也必須使用8080。上述的例子中,因為cas和web都使用了8080端口,因此外網不得不用兩個外網IP地址來分別映射。如果cas和web的端口不同,則可以只使用一個外網IP地址的兩個端口來分別映射,此時外網DNS把兩個域名映射到同一個IP地址上即可。
  遺憾的是,現實中很多單位的IT部門要么難以搞定域名申請和配置,要么因為懶而直接甩鍋給業務系統開發商,要求從應用層去解決,總之因為各種客觀限制,這個最優雅最簡潔的方案卻是最難推動的

解決方案2:通過應用程序端解決

首先說明,這是***絕對不建議***采用的方案。這里僅僅作為一種可能性簡要介紹一下思路。
  回顧上述的失敗流程,因為Web應用對外網用戶給出了錯誤的內網CAS服務地址,從而造成外網用戶無法登錄,那么理論上如果Web應用能夠識別出外網用戶,並讓其重定向到正確的外網CAS地址,則有可能解決此問題。具體來說,需要Web應用通過用戶的HTTP請求中的信息判斷用戶來自哪里(例如是否來自外網與內網之間的網關),區分內網和外網給出不同的重定向地址,並解決后續從CAS跳轉回Web應用地址、網頁中的超鏈接地址動態切換等一系列問題。
  此方法過於麻煩,配置復雜,需要對系統源碼動手,如果CAS后面對接了多個系統(之所以要上SSO,肯定是應用系統數量比較多對不對?),這工作量……更別說如果這些系統的源碼不在你手上或根本就是別的公司開發的,涉及到復雜的協調問題,那就更難進行了。故該方案在此不再展開,后面有參考鏈接,可直接看。本人之所以提這個方案,是因為公司里曾經有程序猿試圖采用這種方式解決,結果遇到剛才說的工作量、第三方協調等問題,搞不下去了……
  下面幾篇文章都是該方案的思路,供參考:

解決CAS內外網雙IP訪問的問題

CAS內外網都能訪問配置說明

CAS和Shiro集成后需要內外網訪問的集成配置

解決方案3(優先考慮):通過Apache2.4/Nginx反向代理

本方案的核心思想是,在內網架設一台Apache/Nginx服務器,通過端口映射向外網用戶提供系統訪問入口,利用Apache的反向代理能力,把內網服務器生成的重定向內網地址,轉換成外網地址,再傳給外網用戶瀏覽器。整個方案不需要對CAS和應用做任何配置或源碼上的修改! 簡單的示意圖如下:

 

配置1:定義VirtualHost,開啟反向代理

在Apache的配置文件中,定義VirtualHost,監聽8085端口,並指定ServerName 113.120.95.60,意思就是該VirtualHost中的配置僅當接收到的請求URL中的主機名是113.120.95.60時才起作用。其他幾個配置就不一一說明了。

# 外網地址
<VirtualHost *:8085>
    ErrorLog "logs/error.8085.out.log"    
    ServerName 113.120.95.60

    # 關閉正向代理
    ProxyRequests Off
    # 反向代理時不保留原始Request中的HOST(即代理服務器自身Host)
    ProxyPreserveHost Off

    <Proxy *>
Require all granted
</Proxy> </VirtualHost>

配置2:反向代理

在VirtualHost中,通過ProxyPassProxyPassReverse兩個指令,實現URL反向代理:

# SSO反向代理
ProxyPass /cas http://192.168.1.68:8585/cas
ProxyPassReverse /cas http://192.168.1.68:8585/cas

# Web應用反向代理
ProxyPass /poweroa http://192.168.1.50:8083/poweroa
ProxyPassReverse /poweroa http://192.168.1.50:8083/poweroa

ProxyPass指示Apache接收到某個路徑請求后,需要把請求轉發給哪一個地址。例如:

ProxyPass /poweroa http://192.168.1.50:8083/poweroa

這句話的意思是,當Apache收到的請求路徑是/poweroa開頭時,需要把請求原封不動地轉發給http://192.168.1.50:8083/poweroa(/sso后面URL的其他部分也會原樣附加上去),然后把http://192.168.1.50:8083/poweroa返回的Response,再原封不動地返回給正在訪問Apache的瀏覽器。這個過程對瀏覽器是透明的,實現了用戶輸入外網地址就能看見內網系統頁面的效果。
  不過,ProxyPass無法處理重定向。當http://192.168.1.50:8083/poweroa的響應報文的HTTP頭中帶有Location重定向標識時,瀏覽器會不折不扣按此標識跳轉。在本文開始的例子中就說過,Web應用返回給訪問者的重定向地址是內網地址。在通過Apache返回時,必須把這個內網地址改成外網地址,這就需要用到ProxyPassReverse指令。例如:

ProxyPassReverse /cas http://192.168.1.68:8585/cas

這句話的意思是,如果Apache接收到的服務器響應中重定向標識Location是http://192.168.1.68:8585/cas,則將其替換為當前用戶訪問地址后加上/cas。這樣,web應用本來是要求瀏覽器重定向到http://192.168.1.68:8585/cas/login?xxxxx,但這個地址在經過Apache返回時被Apache篡改成了http://113.120.95.60:8085/cas/login?xxxxx,瀏覽器並不知道這一切幕后工作,只是單純按照接收到的信息進行重定向,去請求http://113.120.95.60:8085/cas/login?xxxxx。該請求再次經過Apache並被ProxyPass指令轉換成真正的內網CAS服務器地址,從而能夠正確到達CAS服務器。后面過程就不詳述了。
  總之,通過這兩個指令就基本實現了在不對應用系統做任何配置修改的情況下的外網訪問。
  值得一提的是,上面的例子中CAS服務和Web應用的URL都是帶有子路徑/cas和/poweroa的。這里有兩個最佳實踐:

  • 在做反向代理時,最好讓系統運行在某個子路徑下,而不要運行在根路徑下;
  • Apache/Nginx上的反向代理配置,最好配置與后方系統相同的子路徑做為映射路徑;

按上述最佳實踐來做,能夠省掉很多麻煩事。
  

如果系統運行在URL根目錄下,即http://192.168.1.50:8083,而不是http://192.168.1.50:8083/poweroa,在做反向代理配置時就需要把路徑映射到根路徑,如果有多個后方系統都使用根目錄,反向代理將無法區分。有人一定會說使用ServerName、多IP地址、多端口等方式可以實現根據不同請求來源而區分處理,但現實是我遇到的大多數單位提供的對外訪問接口,既沒有域名,也沒有多個IP,甚至也不會給你開多個端口,這種情況下,只能靠子路徑來讓反向代理知道應該到哪里去。那位說了,可是已經部署好的系統就是在根目錄的,我怎么辦?好吧,你施展人格魅力的時候到了,想辦法去說服他們改變吧!如果魅力不夠,也許你可以在內網再架設一個反向代理做二次映射來專門解決這個特定應用的路徑問題,我也沒試過行不行,祝你成功,並且成功后告訴我一聲:
  如果映射的子路徑和系統真實的子路徑不同行不行呢?比如說CAS服務地址是http://192.168.1.68:8585/cas,我在Apache上配置用/cas路徑來映射,即對外的地址為http:// 113.120.95.60:8085/cas,有什么問題?答案是,不一定,但有可能會遇到問題。這個問題通常出在一些系統在開發或部署時,有可能把這個子路徑作為系統變量的一部分,在頁面超鏈接等地方直接使用/cas,因為Apache無法識別和處理這個地址,所以頁面也就無法正常顯示。總而言之,不要自找麻煩,簡單點,生活盡量簡單點

配置3:頁面內容替換

     完成上述反向代理配置后,其實大多數系統就已經能夠正常使用了。不過偶爾還會遇到一些系統在頁面上直接超鏈接其他系統的,比如一個門戶系統在頁面上超鏈接其他業務系統的地址是再正常不過的了。超鏈接只能寫一個URL,要么是內網的,要么是外網的,怎么同時滿足內網用戶和外網用戶呢?前面的反向代理配置,不會處理頁面中的URL,此時就需要用到另一個大殺器:mod-substitute。
  Apache的mod-substitute模塊可以通過正則表達式,實時替換網頁中的內容。具體語法參考請移步原廠:mod-substitute。下面是配置實例:

##去掉GIZP標識,否則無法替換頁面內容
LoadModule headers_module modules/mod_headers.so
RequestHeader unset Accept-Encoding    
## 加載替換模塊,過濾指定類型頁面
LoadModule substitute_module modules/mod_substitute.so
AddOutputFilterByType SUBSTITUTE text/html
AddOutputFilterByType SUBSTITUTE text/plain
AddOutputFilterByType SUBSTITUTE application/json
AddOutputFilterByType SUBSTITUTE application/x-javascript
AddOutputFilterByType SUBSTITUTE application/javascript
AddOutputFilterByType SUBSTITUTE text/javascript
## 把服務器響應中的內網IP地址改成外網地址
Substitute "s|192.168.1.50:8083/poweroa|113.120.95.60:8085/poweroa|n"
 Substitute "s|192.168.1.68:8585/cas|113.120.95.60:8085/cas|n"
 Substitute "s|192.168.1.55:9010/wui|113.120.95.60:8085/wui|n"
 Substitute "s|192.168.1.55:9010/login|113.120.95.60:8085/login|n"

上面的配置中,首先重置Accept-Encoding,這實際上就會去掉瀏覽器發出的請求頭中支持gzip的申明,於是服務器就不會返回經過壓縮的頁面內容,這樣才能夠進行文本替換。AddOutputFilterByType指明要進行替換的Content Type種類,顯然這里應該根據系統的實際情況,列出所有有可能出現系統URL的地方,越少越好,像圖片之類的當然就不需要了,因為這玩意肯定影響性能。Substitute進行實際的內容替換,注意前面的地址是被替換內容,后面的是替換內容,別寫反了啊!
  通過上面的配置后,從外網嘗試打開原來有錯誤鏈接的頁面,看看鏈接地址是不是被改變了?當然你可以拿頁面中的任何內容測試一下,比如把版權信息改成“聖誕節快樂”,把你情敵的姓名改成公司領導姓名,看看用戶反應如何?我就是說說而已,被投訴了可別找我啊!現在知道為什么我說這個東西是大殺器了吧,隨意篡改網頁內容太可怕了有木有!網絡世界真的不能沒有SSL、HTTPS!如果系統本身已經使用HTTPS方式部署,這方法可能會失效,參見后面的專項討論。
  最后強調一下,這個配置會降低性能,對HTTPS可能會無效,僅當網頁內容中出現了系統URL絕對路徑且需要動態替換時才考慮使用!

一個完整的配置文件

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule substitute_module modules/mod_substitute.so
LoadModule headers_module modules/mod_headers.so

NameVirtualHost *:8085

# 外網地址
<VirtualHost *:8085>
    ErrorLog "logs/error.8085.out.log"    
    ServerName 113.120.95.60

    # 關閉正向代理
    ProxyRequests Off
    # 反向代理時不保留原始Request中的HOST(即代理服務器自身Host)
    ProxyPreserveHost Off

    <Proxy *>
        Require all granted
    </Proxy>
    
    ##去掉GIZP標識,否則無法替換頁面內容
    RequestHeader unset Accept-Encoding    
    ## 過濾指定類型頁面
    AddOutputFilterByType SUBSTITUTE text/html
    AddOutputFilterByType SUBSTITUTE text/plain
    AddOutputFilterByType SUBSTITUTE application/json
    AddOutputFilterByType SUBSTITUTE application/x-javascript
    AddOutputFilterByType SUBSTITUTE application/javascript
    AddOutputFilterByType SUBSTITUTE text/javascript
    ## 把服務器響應中的內網IP地址改成外網地址
   Substitute "s|192.168.1.50:8083/poweroa|113.120.95.60:8085/poweroa|n"
 Substitute "s|192.168.1.68:8585/cas|113.120.95.60:8085/cas|n"
  Substitute "s|192.168.1.55:9010/wui|113.120.95.60:8085/wui|n"
  Substitute "s|192.168.1.55:9010/login|113.120.95.60:8085/login|n"
     # SSO反向代理
     ProxyPass /cas http://192.168.1.68:8585/cas
     ProxyPassReverse /cas http://192.168.1.68:8585/cas
 
         
     # poweroa能源web應用反向代理
     ProxyPass /poweroa http://192.168.1.50:8083/poweroa
     ProxyPassReverse /poweroa http://192.168.1.50:8083/poweroa
 
     #oa登錄反向代理
     ProxyPass /login http://192.168.1.55:9010/login
     ProxyPassReverse /login http://192.168.1.55:9010/login
 
     #oa主界面反向代理
     ProxyPass /wui http://192.168.1.55:9010/wui
     ProxyPassReverse /wui http://192.168.1.55:9010/wui
 
     #oa其他部分反向代理
     ProxyPass / http://192.168.1.55:9010/
     ProxyPassReverse / http://192.168.1.55:9010/  
</VirtualHost>

調試技巧

  在調試Apache2.4/Nginx的反向代理配置時,需要熟練掌握一些方法:

  •  啟用瀏覽器的調試功能(按下F12鍵),學會查看Request、Response中的信息,主要就是Header的內容。尤其是在重定向時,注意查看Response中的Location的內容是什么。

 

  • 在Chrome的調試窗口中,要勾選Preserve Log選項(如下圖),否則在頁面跳轉等場合會漏掉跳轉后的請求記錄。

 

 

關於HTTPS

前面的討論,都是建立在內網系統本身是HTTP服務,而不是HTTPS服務的前提上進行的。如果客戶要求通過HTTPS訪問系統,需要如何做呢?這里有兩種方式實現:
應用服務器本身就以HTTPS方式部署
應用服務器以HTTP方式部署,通過Apache/Nginx轉換成HTTPS給用戶訪問;
  第一種方式是網上講解配置SSL的文章采用最多的方式。根據HTTPS的原理特點推測,Apache/Nginx應該無法同時滿足即解密處理HTTP數據,又讓客戶端一無所知(更換證書是屬於客戶端可感知的),因為這種方式就是典型的中間人攻擊。從網上資料來看,Apache是可以進行SSL代理的,搜了一篇 帖子供參考。我想原理上應該就是Apache先用服務器公鑰解密,然后再用Apache上配置的證書重新做加密,與瀏覽器交互。瀏覽器看到的證書是Apache提供的。
  第二種方式是我重點推薦方式。當需要提供HTTPS服務時,只在反向代理Apache/Nginx層進行設置,而源應用總是使用HTTP。這樣做的好處是當業務系統很多且都可以使用子路徑部署方式時,只需要配置一次證書(因為主URL相同,只有子路徑不同),能夠減輕配置工作量。當應用服務器需要集群、多個系統多個域名多個入口等情況下,即使需要配置多個證書,能夠只在反向代理服務器上一次性干完所有工作,不用慢吞吞地重啟Tomcat等應用服務器,畢竟Apache/Nginx都可以秒起甚至熱加載,這是何等幸福!這算是一個最佳實踐吧,以后有時間我可以再寫一篇詳細文章介紹。
  回到本文主題,前面介紹的方案在這兩種場景下是否可以沿用呢?第一種場景我沒有試過,估計可行,如果有人試過請留言說一下結果。第二種場景則是親測通過的,親們可以放心使用。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM