Apache HTTP Server 模組 mod_jk 及其適用於 Microsoft IIS 的 ISAPI 重導器變體使用 AJP 協定將網頁伺服器連接到後端 (通常為 Tomcat)。網頁伺服器會收到 HTTP(S) 要求,而模組會將要求轉送至後端。此功能通常稱為閘道或代理,在 HTTP 的架構中,它稱為反向代理。
反向代理操作指南
簡介
常見問題
反向代理對後端的應用程式並非完全透明。例如,原始用戶端 (例如瀏覽器) 需要對話的主機名稱和埠屬於網頁伺服器,而非後端,因此反向代理會對話不同的主機名稱和埠。當後端的應用程式傳回包含使用其自身後端位址和埠的自參照 URL 的內容時,用戶端通常無法使用這些 URL。
另一個範例是用戶端 IP 位址,對於網頁伺服器而言,它是來源 IP 的輸入連線,而對於後端而言,連線總是來自網頁伺服器。當後端應用程式使用用戶端 IP 位址時,這可能會造成問題,例如出於安全因素。
AJP 作為解決方案
這些問題大多數都由後端的 AJP 協定和 AJP 連接器自動處理。AJP 協定傳輸此通訊中繼資料,而後端連接器會在應用程式使用 Servlet API 方法要求時提供此中繼資料。
下列清單包含 AJP 處理的通訊中繼資料,以及可使用來擷取這些中繼資料的 ServletRequest/HttpServletRequest API 呼叫
- 本機名稱:
getLocalName()
。這也等於getServerName()
,除非要求中包含Host
標頭。在此情況下,伺服器名稱會從該標頭取得。 - 本機 IP 位址:
getLocalAddr()
。最初不支援本機 IP 位址。當將 Apache 或 IIS 的版本 1.2.41 與 Tomcat 版本至少為 6.0.42、7.0.55 或 8.0.11 搭配使用時,即可使用。對於較舊版本或使用 NSAPI 重新導向程式時,getLocalAddr()
會不正確地傳回與getLocalName()
相同的結果。作為解決方法,您可以透過設定JkEnvVar SERVER_ADDR
來轉送本機 IP 位址,然後使用request.getAttribute("SERVER_ADDR")
取代getLocalAddr()
,或使用篩選器包裝要求並以request.getAttribute("SERVER_ADDR")
覆寫getLocalAddr()
。 - 本機埠:
getLocalPort()
。這也等於getServerPort()
,除非要求中包含Host
標頭。在此情況下,如果伺服器埠包含明確埠,或等於所用通訊協定的預設埠,則伺服器埠會從該標頭取得。 - 用戶端位址:
getRemoteAddr()
- 用戶端埠:
getRemotePort()
。最初不支援遠端埠。當將 Apache 或 IIS 的版本 1.2.32 與 Tomcat 版本至少為 5.5.28、6.0.20 或 7.0.0 搭配使用時,即可使用。對於較舊版本或使用 NSAPI 重新導向程式時,getRemotePort()
會不正確地傳回 0 或 -1。作為解決方法,您可以透過設定JkEnvVar REMOTE_PORT
來轉送遠端埠,然後使用request.getAttribute("REMOTE_PORT")
取代getRemotePort()
,或使用篩選器包裝要求並以request.getAttribute("REMOTE_PORT")
覆寫getRemotePort()
。 - 用戶端主機:
getRemoteHost()
- 驗證類型:
getAuthType()
- 遠端使用者:
getRemoteUser()
,如果tomcatAuthentication="false"
- 通訊協定:
getProtocol()
- HTTP 方法:
getMethod()
- URI:
getRequestURI()
- HTTPS 使用:
isSecure()
,getScheme()
- 查詢字串:
getQueryString()
SSLOptions +StdEnvVars
時,mod_jk 才會轉送。對於憑證資訊,您也需要設定 SSLOptions +ExportCertData
。
- SSL 加密:
getAttribute(javax.servlet.request.cipher_suite)
- SSL 金鑰大小:
getAttribute(javax.servlet.request.key_size)
。可以使用JkOptions -ForwardKeySize
停用。 - SSL 客戶端憑證:
getAttribute(javax.servlet.request.X509Certificate)
。如果您想要整個憑證鏈,則您也需要設定JkOptions ForwardSSLCertChain
。在這種情況下,您可能也需要使用工作人員屬性 max_packet_size 調整最大 AJP 封包大小。 - SSL 會話 ID:
getAttribute(javax.servlet.request.ssl_session)
。這是針對 Tomcat,尚未標準化。
微調
不過,在某些情況下這還不夠。假設在您的網路伺服器前面還有另一個較不聰明的反向代理伺服器,例如 HTTP 負載平衡器或類似裝置,它也作為 SSL 加速器。
然後,您確定您的所有客戶端都使用 HTTPS,但您的網路伺服器不知道這一點。它只能看到使用純文字 HTTP 從加速器傳來的請求。
另一個範例會是您網路伺服器前面的簡單反向代理伺服器,因此您的網路伺服器看到的客戶端 IP 位址始終是這個反向代理伺服器的 IP 位址,而不是原始客戶端的 IP 位址。通常,此類反向代理伺服器會產生額外的 HTTP 標頭,例如 X-Forwareded-for
,其中包含原始客戶端 IP 位址(或 IP 位址清單,如果前面有更多串接的反向代理伺服器)。如果我們可以使用此類標頭的內容作為傳遞到後端的客戶端 IP 位址,那就太好了。
因此,我們可能需要處理 AJP 傳送到後端的某些資料。當在 Apache HTTP Server 內部使用 mod_jk 時,您可以使用多個 Apache 環境變數讓 mod_jk 知道它應該轉送哪些資料。這些環境變數可以使用設定指令 SetEnv 或 SetEnvIf 設定,但也可以使用 mod_rewrite 以非常靈活的方式設定(自 Apache 2.x 以來,它不僅可以針對環境變數進行測試,還可以設定它們)。
以下清單包含 mod_jk 在傳送資料到後端之前會檢查的所有環境變數
- JK_LOCAL_NAME: 本機名稱
- JK_LOCAL_PORT: 本機埠
- JK_REMOTE_HOST: 客戶端主機
- JK_REMOTE_ADDR: 客戶端位址
- JK_AUTH_TYPE: 驗證類型
- JK_REMOTE_USER: 遠端使用者
- HTTPS: 開啟(不區分大小寫)以表示使用 HTTPS
- SSL_CIPHER: SSL 加密
- SSL_CIPHER_USEKEYSIZE: SSL 金鑰大小
- SSL_CLIENT_CERT:SSL 客户端证书
- SSL_CLIENT_CERT_CHAIN_:变量名称前缀,包含客户端证书链
- SSL_SESSION_ID:SSL 会话 ID
记住:通常不需要设置这些变量。该模块会自动从 Web 服务器检索数据。只有在需要更改这些数据时,才能使用这些变量进行覆盖。
其中一些变量也可能被其他 Web 服务器模块使用。所有名称不以“JK”开头的变量都由 Apache HTTP 服务器直接设置。如果需要更改数据,但又不想对其他模块的行为产生负面影响,可以将 mod_jk 使用的所有变量的名称更改为私有名称。有关详细信息,请参阅Apache 参考页面。
所有与 SSL 无关的变量仅在版本 1.2.27 中引入。
此外,还有两个特殊快捷方式可以影响转发客户端 IP 地址。使用 JkOptions ForwardLocalAddress
可以将 Web 服务器的本地 IP 地址转发为客户端 IP 地址。这可能很有用,例如在使用 Tomcat 远程地址阀门仅允许来自已注册 Apache HTTP 服务器的连接时。使用 JkOptions ForwardPhysicalAddress
始终将物理对等 IP 地址转发为客户端地址。默认情况下,mod_jk 使用 Web 服务器提供的逻辑地址。例如,模块 mod_remoteip 将逻辑 IP 地址设置为客户端 IP,该 IP 由 X-Forwarded-For
标头中的代理转发。
Tomcat AJP 连接器设置
作为使用上一节中描述的环境变量(仅在使用 Apache 时存在)的替代方案,还可以将 Tomcat 配置为覆盖 mod_jk 转发的部分通信数据。Tomcat 的 server.xml
中的 AJP 连接器允许设置以下属性
- proxyName:服务器名称,由
getServerName()
返回 - proxyPort:服务器端口,由
getServerPort()
返回 - scheme:协议方案,由
getScheme()
返回 - secure:如果希望
isSecure()
返回“true”,则设置为“true”。
URL 处理
URL 重写
有时需要更改应用程序可用 URL 的路径组件。特别是当 Web 应用程序作为某个上下文(例如 /myapp
)部署时,营销人员更喜欢短 URL,因此希望应用程序直接在 http://www.mycompany.com/
下可用。虽然可以将应用程序部署为所谓的 ROOT 上下文(将直接在“/”处可用),但管理员通常更喜欢不使用 ROOT 上下文,例如,因为每个主机只有一个应用程序可以是根上下文。
在反向代理中更改 URL 的过程很繁琐,因为应用程序通常会生成自引用 URL,其中包括尝试向外界隐藏的路径组件。不过,如果确实需要这样做,请按照以下步骤操作。
情况 A:需要让应用程序在简单的 URL 下可用,但如果用户使用更复杂的 URL 也没关系,只要他们不必输入这些 URL 即可。这是简单的情况,如果这对你来说足够了,那么你很幸运。对 Apache HTTP 服务器使用简单的 RedirectMatch
RedirectMatch ^/$ http://www.mycompany.com/myapp/
然后,应用程序将在 http://www.mycompany.com/
下可用,每个访问者都会立即被重定向到真实 URL http://www.mycompany.com/myapp/
案例 B:您需要隱藏傳送至應用程式的所有要求的路徑元件。以下是案例的範例,其中您想要隱藏第一個路徑元件 /myapp
。更複雜的操作留待讀者練習。首先是 Apache HTTP Server 的案例解決方案
1. 使用 mod_rewrite
將 /myapp
新增至所有要求,然後再轉發至後端
# Don't forget the PT flag! (pass through)
RewriteRule ^/(.*) http://www.mycompany.com/myapp/$1 [PT]
2. 使用 mod_headers
改寫應用程式可能傳回的任何 HTTP 重新導向。此類重新導向通常包含您想要隱藏的路徑元件,因為根據 HTTP 標準,重新導向總是需要包含完整 URL,而您的應用程式並不知道您的客戶端透過某些縮短的 URL 與它通訊。HTTP 重新導向是使用稱為 Location
的特殊回應標頭來完成。我們改寫回應的 Location 標頭
# Keep protocol, server and port if present,
# but insert our webapp name before the rest of the URL
Header edit Location ^([^/]*//[^/]*)?/(.*)$ $1/myapp/$2
3. 再次使用 mod_headers
,改寫應用程式可能設定的任何 Cookie 中包含的路徑。此類 Cookie 路徑可能再次包含您想要隱藏的路徑元件。Cookie 是使用稱為 Set-Cookie
的 HTTP 回應標頭來設定。我們改寫回應的 Set-Cookie 標頭
# Fix the cookie path
Header edit Set-Cookie "^(.*; Path=/)(.*)" $1/myapp/$2
3. 部分應用程式可能包含硬式編碼的絕對連結。在這種情況下,請檢查您是否找到一個設定檔項目,以設定您的 Web 架構來設定基本 URL。如果不是,您唯一的方法就是剖析所有回應內容主體,並執行搜尋和取代。這很脆弱,而且非常耗費資源。如果您真的需要執行此操作,您可以使用 mod_proxy_html
、mod_substitute
或 mod_sed
來執行此任務。
如果您使用 Microsoft IIS 作為 Web 伺服器,ISAPI 重新導向器提供一種方法,可以使用內建功能來執行第一步。您為簡單的前置字元變更定義一個對應檔案,如下所示
# Add a context prefix to all requests ...
/=/myapp/
# ... or change some prefix ...
/oldapp/=/myapp/
然後將檔案名稱放入登錄或 isapi_redirect.properties
檔案的 rewrite_rule_file
條目中。在您的 uriworkermap.properties
檔案中,您仍然需要對 URL 進行對應,就像在改寫之前一樣!
可以使用同一個檔案,但使用正規表示法來執行更複雜的改寫。前導波浪號 '~
' 表示您正在使用正規表示法
# Use a regular expression rewrite
~/oldapps([0-9]*)/=/newapps$1/
不支援步驟 2(改寫重新導向回應)或步驟 3(改寫 Cookie 路徑)。
URL 編碼
某些類型的問題是由編碼 URL 的使用觸發的(請參閱百分比編碼)。對於同一個位置,存在許多不同的 URL 是等效的。反向代理需要檢查 URL 以套用自己的驗證規則,並決定應將請求傳送至哪個後端(或是否應自行處理)。因此,請求 URL 首先會正規化:百分比編碼字元會解碼,/./
會替換為 /
,/XXX/../
會替換為 /
,並對 URL 進行類似的處理。之後,網路伺服器可能會套用改寫規則,以較不顯而易見的方式進一步變更 URL。最後,沒有更多方法可以將結果 URL 放入編碼中,該編碼「類似」於用於原始 URL 的編碼。
由於歷史原因,mod_jk 和 ISAPI 外掛程式在將結果 URL 傳送至後端之前,編碼結果 URL 的方式有幾個備選方案。它們可以透過 JkOptions
(mod_jk) 或 uri_select
(ISAPI) 選擇。不建議使用這些歷史編碼,因為它們會產生負面的功能影響或構成安全風險。自 1.2.24 版以來的預設編碼為 ForwardURIProxy
(mod_jk) 或 proxy
(ISAPI),強烈建議保留預設值並移除所有舊的明確設定。
請求屬性
在使用 Apache HTTP 伺服器時,您也可以將更多屬性新增至您轉發的任何請求。為此,請使用 JkEnvVar
指令(有關詳細資訊,請參閱Apache 參考頁面)。此類請求屬性可以在 Tomcat 端透過 request.getAttribute(attributeName) 擷取。請注意,透過 mod_jk 設定的屬性名稱不會列在 request.getAttributeNames() 中!