「瀏覽器開得了外部網站,只有業務應用程式到不了外部 API。」「開發機沒問題,到客戶網路就逾時。」「手動執行會通訊,一做成 Windows 服務就失敗。」── 在有企業內部 Proxy 的環境跑業務應用程式時,這類諮詢最常見。
多數情況下,原因既不是 Proxy 伺服器故障,也不是應用程式錯誤。Windows 有好幾套彼此分開、人們都叫「Proxy 設定」的系譜,誰讀哪一套會依應用程式(它用的 HTTP 堆疊)與執行帳戶而不同── 那就是不一致。瀏覽器讀的設定、服務讀的設定、.NET HttpClient 讀的設定,可以各自是不同的東西。結構一旦進了腦子,「瀏覽器可以,但是……」的隔離會意外地快。
本文面向中小企業 IT 人員與 Windows 應用程式開發者,把 Proxy 設定的三個系譜──WinINET、WinHTTP、環境變數──以及 PAC 與 WPAD 自動設定、.NET Framework 與 .NET(Core 以後)的 Proxy 解析差異、需驗證的 Proxy(407)、TLS 檢查與實務隔離步驟,收成同一張圖。HttpClient 的建立模式與逾時設計本身見「不要用 using 包住 HttpClient」,本文專注於 Proxy 解析。
1. 先講結論
- Windows 的 Proxy 設定不是一套,至少有三個系譜。 (1) WinINET 每位使用者設定(設定應用程式的「Proxy」頁 = 舊的網際網路選項),(2) WinHTTP 電腦設定(
netsh winhttp),(3)HTTP_PROXY/HTTPS_PROXY環境變數。讀哪一套由應用程式端決定。12 - 設定應用程式裡看到的「Proxy」是 WinINET 的每位使用者設定。 瀏覽器與互動式應用程式會讀;Windows 服務不會。WinINET 不支援在服務中使用,服務用途是 WinHTTP 的工作。13
- 「手動可以、做成服務就不行」最常見的原因是執行帳戶不同。 LocalSystem 與服務帳戶看不到管理員在自己畫面上設好的每位使用者 Proxy。34
netsh winhttp set proxy是靜態設定,不處理 PAC、自動偵測或 Proxy 驗證。 若要依電腦設定 PAC 或 WPAD,需要netsh winhttp set advproxy這一側。42- PAC 結果依 URL 而變。 PAC 檔的
FindProxyForURL函式接受 URL 與主機,回傳 Proxy 清單或直接連線(DIRECT)。「那個網站可以,只有這個 API 不行」可能是 PAC 分支。56 - .NET(Core 以後)的 HttpClient 以環境變數 → Windows 使用者 Proxy 設定的順序初始化預設 Proxy。 只要定義了
HTTP_PROXY、HTTPS_PROXY或ALL_PROXY任一項,就優先於作業系統設定,因此會發生「有人把環境變數留著」的事故。7 - .NET Framework 的預設是執行帳戶的網際網路選項,可用 app.config 的
defaultProxy覆寫。 設定檔設定優先於系統設定。89 - 407 是 Proxy 驗證錯誤,與 401(伺服器驗證)是兩件事。 配置包含 Negotiate、NTLM、Basic,.NET 用
DefaultProxyCredentials或WebProxy.UseDefaultCredentials傳入認證。請注意,在服務帳戶下「預設認證」的內容會變。101112 - TLS 檢查 Proxy 必須與內部 CA 憑證發佈成套才站得住。 沒收到的電腦與執行階段會得到憑證驗證錯誤。用發佈到憑證存放區解決,不要在應用程式裡關掉驗證。134
一句話:每次說「我查過 Proxy 設定」時,都要能說出查的是三個系譜裡的哪一個、從哪個帳戶查── 這就是本文的主題。
2. Windows 有三個系譜的「Proxy 設定」
先看整張地圖。Windows 應用程式尋找企業內部 Proxy 的路徑,落在這三個系譜。
| 設定系譜 | 設定位置 / 命令 | 範圍 | 主要誰讀 |
|---|---|---|---|
| (1) WinINET(網際網路選項) | 設定 → 網路和網際網路 → Proxy,inetcpl.cpl |
每位使用者(預設) | 瀏覽器、互動式桌面應用程式、.NET Framework 預設 |
| (2) WinHTTP(電腦設定) | netsh winhttp set proxy / set advproxy |
電腦 | Windows 服務、部分作業系統元件 |
| (3) 環境變數 | HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY |
處理程序(依定義位置繼承) | Core 以後的 HttpClient、curl、Node.js 與 Python 等跨平台工具 |
(1) 就是一般人認定的「Windows Proxy 設定」,實質是 WinINET 組態。歷史上是 Internet Explorer 的網際網路選項,預設依使用者儲存。4
(2) 是「沒有登入使用者」這類服務情境的電腦預設值。(3) 主要是來自跨平台世界的工具慣例;在 Windows 上,.NET(Core 以後)與 curl 等也會讀。7
重點是:讀哪個系譜由應用程式端決定,不是由設定端決定。 應用程式內部用 WinINET 就讀 (1);用 WinHTTP 就讀 (2)(或應用程式自己的覆寫);Core 以後的 .NET 則先 (3) 再 (1)。所以通常不是「Proxy 設定是對的卻連不上」,現實是「應用程式讀的系譜,跟你查的那一套不是同一套」。
flowchart TB
accTitle: Windows Proxy 設定的三個系譜
accDescr: WinINET 是每位使用者的設定與網際網路選項,WinHTTP 是經由 netsh 的電腦預設值,環境變數是處理程序範圍。讀哪個系譜由應用程式決定,不是由設定端決定
fam{"哪個系譜?"}
fam --> wininet["WinINET 每位使用者設定"]
fam --> winhttp["WinHTTP 電腦設定"]
fam --> env["HTTP_PROXY 與其同類"]
wininet -.-> r1["瀏覽器與桌面應用程式"]
winhttp -.-> r2["服務與部分作業系統"]
env -.-> r3[".NET Core+ 與 curl"]
圖 1: 三個系譜並列。應用程式選擇讀哪一套。
若啟用群組原則「將 Proxy 設定設為電腦層級(而非使用者層級)」,可以把 (1) 改成電腦層級並套用到每位使用者。用 MDM(Intune 等)可用 NetworkProxy CSP 依裝置設定。4
3. WinINET 與 WinHTTP ── 互動式應用程式用、服務用
3.1. 角色差異
WinINET 與 WinHTTP 都是 Windows 內建的 HTTP 用戶端堆疊,但假設的用途不同。
- WinINET:針對互動式桌面應用程式。它會自動繼承使用者的網際網路選項(Proxy、Cookie、認證快取),必要時還能顯示認證輸入 UI。不支援在服務或類似服務的處理程序中使用。1
- WinHTTP:針對服務與伺服器端。它支援以服務帳戶執行、執行緒模擬與工作階段隔離;相對地不分享使用者的瀏覽器設定、Cookie 或認證。也不顯示 UI。3
Microsoft 自己的指引同樣清楚:「除非你在服務裡、或在需要工作階段隔離與模擬的類似服務處理程序裡執行,否則使用 WinINET」── 反過來說,若是服務,就用 WinHTTP。1
flowchart TB
accTitle: 互動式應用程式用 WinINET,服務用 WinHTTP
accDescr: WinINET 繼承已登入使用者的網際網路選項,且不支援在服務中使用。WinHTTP 以服務帳戶、無 UI 執行,且不分享使用者的瀏覽器設定
q{"互動式桌面應用程式?"}
q -->|"是"| ie["WinINET"]
q -->|"服務或類似服務"| wh["WinHTTP"]
ie -.-> ieNote["讀使用者的網際網路選項"]
wh -.-> whNote["電腦設定,無 UI"]
圖 2: 互動式應用程式用 WinINET。服務用 WinHTTP。
3.2. 基本的 netsh winhttp 操作
WinHTTP 的電腦預設 Proxy 用 netsh 操作。2
:: Display the current WinHTTP proxy settings
netsh winhttp show proxy
:: Set a static proxy (with a bypass list)
netsh winhttp set proxy proxy-server="proxy.example.co.jp:8080" bypass-list="*.example.co.jp;<local>"
:: Import the Internet Options (WinINET) settings
netsh winhttp import proxy source=ie
:: Return to the default (DIRECT)
netsh winhttp reset proxy
這裡要記住兩個限制。
netsh winhttp set proxy是靜態設定。 它既不處理 Proxy 自動偵測,也不處理指定 PAC URL,也不處理 Proxy 驗證。4import proxy source=ie只複製當下那一刻的靜態設定,不會跟隨之後網際網路選項端的變更。需要包含 PAC 或自動偵測的電腦層級組態時,用netsh winhttp set advproxy設定 JSON 形式的詳細設定(Proxy、ProxyBypass、AutoconfigUrl、AutoDetect)。2
3.3. 最常見的陷阱:服務不讀使用者的 IE 設定
現場最常看到的模式,依時間順序是這樣。
- 開發者在自己的電腦跑工具 → 每位使用者 Proxy 設定 (1) 生效,於是成功
- 正式環境以 LocalSystem 做成常駐的 Windows 服務(Windows 服務的建立與維運)
- 從 LocalSystem 看得到的設定是另一套(每位使用者設定看不見,WinHTTP 電腦設定未設定 = DIRECT)→ 嘗試直接連外部 API 並逾時
不是「同一台電腦卻不行」;即使同一台電腦,執行帳戶不同,看得見的 Proxy 設定集合就不同。對即使沒有使用者登入也要通訊的處理程序,正確作法是以該處理程序的 HTTP 堆疊實際會讀的形式準備電腦層級設定。使用 WinHTTP 的原生應用程式或 Windows 元件會套用 netsh 的 WinHTTP 設定。4 另一方面,Core 以後的 HttpClient 不讀 WinHTTP 的電腦設定(見第 5 章),因此對 .NET 服務要設系統環境變數(HTTPS_PROXY 等),或從應用程式設定明確指定 HttpClientHandler.Proxy。
反向事故也會發生。若用 netsh winhttp set proxy 把靜態 Proxy 烤進在企業網路與外部之間移動的筆電,公司外連不到那個 Proxy,通訊就整段死掉。把電腦靜態設定當成針對網路組態不變的伺服器的手段。4
flowchart TB
accTitle: 為什麼服務看不到使用者的 IE 設定
accDescr: 開發者執行會讀每位使用者的 WinINET 設定並成功。以 LocalSystem 那些設定看不見。原生 WinHTTP 應用程式接著跟隨未設定的電腦設定(DIRECT)。.NET Core+ 服務仍使用環境變數或明確的 handler.Proxy,不會改去讀 netsh winhttp
dev["以使用者手動執行"] --> ok["套用 WinINET 每位使用者設定"]
svc["LocalSystem 的 Windows 服務"] --> miss["每位使用者設定看不見"]
miss --> stack{"哪一套 HTTP 堆疊?"}
stack -->|"WinHTTP"| direct["WinHTTP 未設定 = DIRECT"]
stack -->|".NET Core+"| env["環境變數或 handler.Proxy"]
direct --> fail["外部 API 逾時"]
圖 3: 同一台電腦、不同帳戶,看得見的 Proxy 設定集合就不同。
4. PAC 與 WPAD ── 「自動設定」實際是什麼
4.1. PAC 檔與 FindProxyForURL
PAC(Proxy Auto-Configuration)檔是計算「這個 URL 該用哪個 Proxy」的 JavaScript(ECMAScript),且一定包含名為 FindProxyForURL(url, host) 的函式。函式回傳應使用的 Proxy 清單,或表示可以不經 Proxy 直接連線的特殊回傳值(DIRECT)。5
function FindProxyForURL(url, host) {
// Internal domains and private addresses go direct
if (dnsDomainIs(host, ".example.co.jp") ||
isInNet(host, "10.0.0.0", "255.0.0.0")) {
return "DIRECT";
}
// Everything else goes through a proxy. Fall back to the next if the first is unavailable
return "PROXY proxy1.example.co.jp:8080; PROXY proxy2.example.co.jp:8080; DIRECT";
}
接著有兩個實務後果。
- Proxy 解析必須依 URL 進行。 因為 PAC 可依 URL(主機)回傳不同 Proxy 或直接連線,WinHTTP 的自動 Proxy 功能也設計成每次傳入請求 URL 再查詢。6「瀏覽器看得到另一個網站」並不能證明問題 API 走同一條路。
- DIRECT 是「不經 Proxy 走」的指示。 若本應走內部的流量從未出現在 Proxy 記錄,先懷疑 PAC 回了 DIRECT(或命中略過清單)。
4.2. 經由 WPAD 的自動偵測
開啟「自動偵測設定」後,機器會用 WPAD(Web Proxy Auto-Discovery)通訊協定尋找 PAC 檔位置。典型組態是 DHCP 發放 PAC URL,或用 DNS 查名為 wpad 的主機,再從 http://wpad/wpad.dat 這類 URL 下載 PAC。14
也就是說,「自動偵測」不是魔法,而是只有在 DHCP/DNS 已經備好 WPAD 安排的網路上才會運作的機制。在沒有這種安排的網路上只開自動偵測,只會多出偵測失敗的等待時間。
flowchart TB
accTitle: PAC 依 URL 決定 Proxy,WPAD 只找 PAC
accDescr: FindProxyForURL 接受 URL 與主機並回傳 Proxy 清單或 DIRECT。WPAD 只經 DHCP 或 DNS 找出 PAC。無法評估 PAC 的用戶端回落到靜態 Proxy 或環境變數
url["請求 URL"] --> pac["FindProxyForURL"]
pac -->|"Proxy 清單"| via["經由 Proxy"]
pac -->|"DIRECT"| dir["不經 Proxy 連線"]
wpad["經 DHCP 或 DNS 的 WPAD"] -.-> pac
nopac["無法評估 PAC 的用戶端"] -.-> fb["靜態設定或環境變數"]
圖 4: PAC 依 URL 決定。WPAD 只找 PAC 檔。
4.3. 無法評估 PAC 的用戶端如何表現
不是每個用戶端都能評估 PAC。
netsh winhttp set proxy的靜態設定不評估 PAC。4- 使用
HTTP_PROXY環境變數風格的工具,原則上只能寫固定 Proxy URL(沒有地方寫 PAC URL)。7 - 直接使用 WinHTTP 的原生應用程式,取決於工作階段怎麼開。Windows 8.1 以後用
WinHttpOpen並指定WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY開啟的應用程式,WinHTTP 會依每個請求自動解析系統/使用者 Proxy 設定(含 WPAD/PAC)。15 若以較舊的WINHTTP_ACCESS_TYPE_DEFAULT_PROXY(自 8.1 起淘汰)等開啟,自動 Proxy 不會整合進 HTTP 堆疊,應用程式必須自己呼叫WinHttpGetProxyForUrl再把結果套到請求。也就是說,在較舊的實作上,PAC 可以存在卻仍未被使用。5
「瀏覽器經 PAC 走到正確 Proxy,業務應用程式不讀 PAC、嘗試直接連線而失敗」── 這是另一個經典不一致。在以 PAC 營運的網路上,必須為無法讀 PAC 的用戶端決定後援──靜態設定或環境變數。
5. .NET 的 Proxy 解析 ── Framework 與 Core 以後是兩回事
.NET 應用程式讀哪一套 Proxy 設定,.NET Framework 與 .NET(Core 以後)的預設不同。把兩者搞混,就會用 Framework 時代的知識去查 .NET 8 應用程式而漏掉。
5.1. .NET Framework ── 預設是網際網路選項,用 defaultProxy 覆寫
在 .NET Framework,HttpWebRequest 與坐在它上面的 HttpClient 若未明確指定 Proxy,就使用預設 Proxy。預設 Proxy 由系統的網際網路設定(執行帳戶的 WinINET 設定)與設定檔組合決定,且設定檔設定優先。8
可用 app.config(或 machine.config)的 system.net/defaultProxy 元素控制這個預設。9
<configuration>
<system.net>
<!-- useDefaultCredentials: whether to send default credentials to an authenticating proxy -->
<defaultProxy enabled="true" useDefaultCredentials="true">
<proxy usesystemdefault="true"
proxyaddress="http://proxy.example.co.jp:8080"
bypassonlocal="true" />
<bypasslist>
<add address="[a-z]+\.example\.co\.jp$" />
</bypasslist>
</defaultProxy>
</system.net>
</configuration>
讓 defaultProxy 元素為空就使用系統(網際網路選項)設定;寫上 proxyaddress 等則那些優先。從程式可用 WebRequest.DefaultWebProxy 取代同一個預設。98
第 3.3 節的陷阱這裡也適用。因為預設是「執行帳戶的網際網路選項」,以服務帳戶執行的 .NET Framework 應用程式讀到的是與管理員桌面看得見的不同(通常是空的)設定集合。
5.2. .NET(Core 以後)── 環境變數優先,然後才是作業系統使用者設定
Core 以後的 HttpClient 有靜態屬性 HttpClient.DefaultProxy。除非處理常式明確指定 Proxy,每個 HttpClient 執行個體都用它。Windows 上的初始化規則是「讀環境變數,若未定義則讀使用者 Proxy 設定」。7
使用的環境變數如下。7
| 環境變數 | 意義 |
|---|---|
HTTP_PROXY |
HTTP 請求使用的 Proxy |
HTTPS_PROXY |
HTTPS 請求使用的 Proxy |
ALL_PROXY |
上述未定義時的後援 |
NO_PROXY |
不應使用 Proxy 的主機,逗號分隔清單 |
三件要注意的事。
- 只要定義了
HTTP_PROXY、HTTPS_PROXY或ALL_PROXY任一項,就優先於作業系統端的 Proxy 設定。 只定義NO_PROXY並不會從環境變數構成 Proxy,在 Windows 上會繼續使用作業系統使用者 Proxy 設定。舊實驗後把HTTPS_PROXY留成系統環境變數、或 CI/CD 範本注入它,這類「看不見的設定」是事故溫床。 NO_PROXY不支援萬用字元(*)。 要比對子網域,放前導點(.example.com比對www.example.com,但不比對example.com本身)。7- 在非 Windows(Linux 容器等),若環境變數未定義,會以沒有 Proxy初始化。同一應用程式在 Windows 與 Linux 之間預設行為改變,是容器遷移時要確認的事。7
5.3. 明確指定 ── HttpClientHandler.Proxy 與 UseProxy
無論哪個執行階段,最高優先都是處理常式上的明確指定。指定 HttpClientHandler.Proxy 優先於作業系統設定與設定檔,UseProxy = false 則完全不使用 Proxy。14
using System.Net;
// Use a proxy read from app settings explicitly
var handler = new HttpClientHandler
{
Proxy = new WebProxy("http://proxy.example.co.jp:8080")
{
BypassProxyOnLocal = true,
BypassList = new[] { @"^intra\.example\.co\.jp$" },
UseDefaultCredentials = true // On an authenticating proxy, respond with the running account's credentials
},
UseProxy = true
};
var client = new HttpClient(handler);
// A client that never uses a proxy (for direct internal APIs)
var directHandler = new HttpClientHandler { UseProxy = false };
var directClient = new HttpClient(directHandler);
沒有明確指定、跟隨作業系統設定時,本機目的地的自動略過有規則。沒有點的平面名稱、迴路位址、符合機器本身網域後綴的目的地等,可被當成「本機」。14「指定 IP 位址行為就變了」或「改用 FQDN 突然開始走 Proxy」這類現象,可能由這個判斷造成。
優先順序如下。
| 優先(高 → 低) | .NET Framework | .NET(Core 以後) |
|---|---|---|
| 1 | HttpClientHandler.Proxy 等明確指定 |
相同 |
| 2 | app.config 的 defaultProxy |
對 HttpClient.DefaultProxy 的指派 |
| 3 | 執行帳戶的網際網路選項 | 環境變數(HTTP_PROXY 等) |
| 4 | — | Windows 使用者 Proxy 設定 |
flowchart TB
accTitle: Framework 與 Core 以後的預設 Proxy 解析
accDescr: 明確的 HttpClientHandler.Proxy 永遠勝出。Framework 接著使用 app.config defaultProxy 與執行帳戶的網際網路選項。Core 以後使用對 HttpClient.DefaultProxy 的指派,然後環境變數,然後 Windows 使用者 Proxy 設定
expl["明確的 handler.Proxy"] --> done["使用該 Proxy"]
noexpl["沒有明確 Proxy"] --> fw{"哪個執行階段?"}
fw -->|"Framework"| cfg["app.config defaultProxy"]
cfg --> ie["執行帳戶網際網路選項"]
fw -->|"Core 以後"| dp["HttpClient.DefaultProxy"]
dp --> ev["HTTP_PROXY 與其同類"]
ev --> user["Windows 使用者 Proxy 設定"]
圖 5: 明確指定永遠勝出。預設路徑依執行階段而不同。
6. 需驗證的 Proxy ── 407 是 Proxy 的驗證錯誤
6.1. 不要把 407 與 401 搞混
當你嘗試通過要求驗證的 Proxy 時,Proxy 會回傳狀態碼 407(Proxy Authentication Required) 與列出可用配置的 Proxy-Authenticate 標頭。那與目的地伺服器的驗證要求(401 與 WWW-Authenticate)是兩件事;你交出認證的對象與設定它們的位置都不同。10
flowchart TB
accTitle: 407 是 Proxy,401 是目的地伺服器
accDescr: 407 與 Proxy-Authenticate 來自 Proxy。401 與 WWW-Authenticate 來自目的地伺服器。認證與設定位置都不同
req["對外請求"] --> who{"誰要求驗證?"}
who -->|"Proxy"| e407["407 + Proxy-Authenticate"]
who -->|"目的地"| e401["401 + WWW-Authenticate"]
e407 -.-> cred["DefaultProxyCredentials"]
圖 6: 407 是 Proxy 驗證。401 是伺服器驗證。
配置包含原樣送出使用者名稱與密碼的 Basic,以及 Negotiate(Kerberos/NTLM)這類挑戰/回應配置。挑戰/回應配置中密碼本身不走網路,驗證經數次往返完成。10 它「回退」到哪個配置的機制,在「圖解 NTLM 與 Kerberos」有更細的說明。
6.2. 在 .NET 如何傳入認證
若要用來自作業系統設定的預設 Proxy、只讓驗證通過,使用 HttpClientHandler.DefaultProxyCredentials。這些是在 UseProxy = true 且 Proxy = null(= 系統預設 Proxy)時,送給該預設 Proxy 的認證。11
using System.Net;
var handler = new HttpClientHandler
{
UseProxy = true, // The default. Combined with a null Proxy, this uses the system-default proxy
Proxy = null,
// Respond to 407 with the credentials of the running account (signed-in user or service account)
DefaultProxyCredentials = CredentialCache.DefaultCredentials
};
var client = new HttpClient(handler);
明確指定 Proxy 時,把認證放在 WebProxy 這一側。許多用戶端情境的建議是使用已登入使用者的預設認證,而不是個別使用者名稱與密碼,WebProxy.UseDefaultCredentials = true 就是那個。12
6.3. 服務帳戶的 407 問題
執行帳戶在這裡也很重要。「預設認證」意指正在執行該處理程序的帳戶的認證。以互動式使用者執行,對 Proxy 的驗證就是該使用者;以 LocalSystem 服務執行,就是電腦帳戶。
- 若 Proxy 經 Active Directory 驗證使用者,它無法驗證電腦帳戶或本機帳戶,一做成服務 407 就持續
- 反過來說,有些環境在 Proxy 端對服務有驗證豁免(依來源 IP 或依帳戶)
因此 407 調查不能只停在「應用程式的設定」;它與基礎建設端的設計確認成套:Proxy 能否驗證執行帳戶。對即將做成服務的應用程式,設計階段就應決定其一:以網域服務帳戶(gMSA 等)執行、在 Proxy 端放驗證豁免,或架設不要求驗證的內部轉送 Proxy。
也有把認證嵌進環境變數的風格,如 HTTP_PROXY=http://user:pass@proxy:80807,但明文密碼會暴露在環境變數(= 處理程序資訊)裡,不建議作為常態營運。
7. HTTPS 與 Proxy ── CONNECT 通道與 TLS 檢查
7.1. HTTPS 以「通道」通過 Proxy
對 HTTPS 使用 Proxy 時,用戶端先向 Proxy 送 CONNECT destination-host:443 請求,Proxy 開啟 TCP 通道。成功時 Proxy 回 200,之後用戶端與目的地伺服器在該通道內進行 TLS 交握。若通道打不開,Proxy 回 407(需要驗證)、502 等。16
這個模型裡 Proxy 讀不到通道內容(加密的 HTTPS)。Proxy 記錄留下的是目的地主機名稱與連線是否成功;URL 路徑看不見── 那就是「直通」Proxy 的行為。
flowchart TB
accTitle: 經 Proxy 的 HTTPS 是 CONNECT 通道
accDescr: 用戶端向 Proxy 送 CONNECT,Proxy 開啟 TCP 通道並回 200,然後用戶端與目的地在通道內進行 TLS 交握。Proxy 記錄看到主機,看不到 URL 路徑
cli["用戶端"] -->|"CONNECT host:443"| px["Proxy"]
px -->|"200 與 TCP 通道"| dest["目的地"]
dest -->|"通道內的 TLS"| cli
px -.-> log["記錄:只有主機與成功"]
圖 7: 直通 Proxy 看得到主機,看不到加密路徑。
7.2. TLS 檢查 Proxy 與憑證錯誤
另一方面,資安產品 Proxy 包含TLS 檢查(SSL 解密、中斷並檢查)類型,它終止 TLS、檢查內容、再加密後轉送。這個機制裡呈現給用戶端的伺服器憑證不是真的那一張,而是被換成以 Proxy 自己的 CA 重新簽署的憑證。13
因此這個組態站得住的前提是「Proxy 的 CA 憑證已發佈到每個用戶端的信任根」。沒收到的電腦,或不看 Windows 憑證存放區的執行階段(有自己信任存放區的工具),會得到憑證驗證錯誤。在 .NET 通常表面為包著 AuthenticationException 的 HttpRequestException(「遠端憑證無效」那類訊息)。
修正原則如下。
- 把內部 CA 憑證發佈到本機電腦的「受信任的根憑證授權單位」存放區。 使用者存放區與電腦存放區的分界見「Windows 憑證存放區實務指南」。
- 不要在程式裡關掉憑證驗證。 讓
ServerCertificateCustomValidationCallback永遠回 true 的權宜之計,一到外部網路就變成無法偵測中間人的脆弱應用程式。 - 憑證釘選流量本來就無法檢查。 像部分 Windows 元件那樣驗證特定 Microsoft 憑證的連線,Proxy 一換憑證就失敗,除了排除沒有其他辦法。4 對前往 Microsoft 365 等 SaaS 的流量,Microsoft 自己建議從網路層解密與檢查排除。13
「每個內部網站都看得到,只有某個雲端服務在應用程式裡出現憑證錯誤」這類症狀,應先懷疑 TLS 檢查排除清單與釘選的組合。
flowchart TB
accTitle: TLS 檢查 Proxy 會重新簽署憑證
accDescr: Proxy 終止 TLS、檢查內容,並呈現以自己 CA 重新簽署的憑證。只有該 CA 在信任根裡,驗證才成立。不要在程式裡關掉驗證
real["真正的伺服器憑證"] --> px["TLS 檢查 Proxy"]
px --> fake["由 Proxy CA 重新簽署"]
fake --> client["用戶端驗證"]
client -->|"CA 在信任根"| ok["成功"]
client -->|"缺少 CA"| err["憑證錯誤"]
err -.-> fix["把 CA 發佈到存放區"]
圖 8: 檢查只有與發佈內部 CA 成套才運作。
8. 隔離步驟 ── 找出元兇的五步
依這個順序機械地調查「連不上」。
| 步驟 | 做什麼 | 學到什麼 |
|---|---|---|
| (1) 重現 | 用 curl.exe -v 或 Invoke-WebRequest 存取問題 URL(最好同一台電腦、同一個帳戶) |
是應用程式特有問題還是環境問題 |
| (2) 收集設定 | 收集三個系譜:netsh winhttp show proxy、每位使用者設定、環境變數 |
哪個系譜裡有什麼 |
| (3) 識別帳戶 | 識別目標應用程式的執行帳戶(服務、工作排程器、其他使用者) | 它以哪些設定與哪些認證執行 |
| (4) 分類錯誤 | 區分 407 / 403 / 名稱解析失敗 / 逾時 / 憑證錯誤 | 隔離 Proxy 驗證、原則拒絕、路徑與 TLS 檢查 |
| (5) Proxy 記錄 | 在 Proxy 伺服器存取記錄核對對應時間 | 到底有沒有到 Proxy、驗證成誰 |
(2) 可用 PowerShell 一次收集。
# (1) Per-user (WinINET) settings — note that this reads HKCU of the running account
Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' |
Select-Object ProxyEnable, ProxyServer, ProxyOverride, AutoConfigURL
# (2) Machine (WinHTTP) settings
netsh winhttp show proxy
# (3) Environment variables
Get-ChildItem env: | Where-Object Name -match 'proxy'
幾則實務提示。
- (1) 的重現測試裡,要意識工具讀的是哪個設定系譜。Windows 內建
curl.exe可用-x http://proxy:8080明確指定 Proxy,TLS 驗證通常用作業系統憑證存放區(Schannel)。Windows PowerShell 5.1 的Invoke-WebRequest跟隨 .NET Framework 這一側(預設網際網路選項);PowerShell 7 跟隨 .NET 這一側(環境變數優先)。「curl 可以、應用程式不行」本身就是設定系譜不一致的線索。 - 若 (3) 的目標是服務,在與服務相同的帳戶下重查 (1) 與 (2)。管理員自己工作階段裡的檢查,不是 LocalSystem 看見什麼的證據。
- (4) 的錯誤分類裡,407 的第一候選人是第 6 章(驗證),憑證錯誤是第 7 章(TLS 檢查),逾時是「沒到 Proxy」(路徑、名稱解析、防火牆)。原因是 Windows 防火牆傳入規則而不是 Proxy 的模式,見「Windows 防火牆與業務應用程式」。
- 若走到 (5) Proxy 記錄仍無痕跡,流量從未到達 Proxy。懷疑 PAC 的 DIRECT 決定、略過清單或殘留環境變數,必要時用封包擷取確認實際目的地(「Windows 的封包擷取實務 ── 在 pktmon、netsh trace 與 Wireshark 之間選擇」)。
flowchart TB
accTitle: 隔離 Proxy 故障的五個步驟
accDescr: 在同一帳戶下重現、收集三個系譜的設定、識別執行帳戶、分類錯誤,然後查 Proxy 記錄
s1["用 curl 重現"] --> s2["收集三個系譜"]
s2 --> s3["識別帳戶"]
s3 --> s4["分類錯誤"]
s4 --> s5["查 Proxy 記錄"]
s4 -.-> e407["407:驗證"]
s4 -.-> ecert["憑證錯誤:檢查"]
s4 -.-> eto["逾時:從未到達"]
圖 9: 依序走完五步。錯誤類別決定下一章。
9. 設計建議 ── 把應用程式做成「可以設定 Proxy」的
把調查步驟反過來,就成了應用程式端的設計指引。對要交到有企業內部 Proxy 環境的 Windows 應用程式,建議如下。
- 讓 Proxy 可從應用程式設定組態。預設是「跟隨作業系統設定」。 多數環境預設就夠;只有在讀不到 PAC、以服務執行、特殊 Proxy 組態這些例外環境,才從設定檔指定 Proxy URL、略過清單與「不使用 Proxy」。第 5.3 節的
HttpClientHandler.Proxy/UseProxy是實作點。14 - 寫下內部目的地(API、資料庫、授權伺服器等)如何當成 Proxy 例外處理。 以能寫進部署程序的形式記錄:是用 PAC DIRECT、略過清單還是
NO_PROXY排除。NO_PROXY的比對規則(沒有萬用字元、前導點代表什麼)被廣泛誤解,請附上例子。7 - 逾時與重試要假設會經過 Proxy 來設計。 若 Proxy 掛掉或卡在驗證,等很長預設逾時的實作會讓 UI 與營運一起凍結。分開較短的連線逾時,並把重試限於冪等請求(設計細節見「不要用 using 包住 HttpClient」)。
- 記錄「用了哪個 Proxy」。 讓應用程式自己能回答故障調查的第一個問題。
像 (4) 這樣的記錄,只記下解析結果就已經有效。重點是從你實際用來組態用戶端的設定(處理常式)推导路徑。若直接記錄 HttpClient.DefaultProxy,當處理常式明確指定 Proxy 或設 UseProxy = false 時,會記下與實際路徑不一致的值。
using System.Net.Http;
// handler is the same instance used to create the HttpClient
// UseProxy=false is always direct. An explicit specification wins; otherwise DefaultProxy is used
var effectiveProxy = handler.UseProxy
? handler.Proxy ?? HttpClient.DefaultProxy
: null;
var target = new Uri("https://api.example.com/v1/orders");
var route = effectiveProxy is null || effectiveProxy.IsBypassed(target)
? "DIRECT"
: effectiveProxy.GetProxy(target)?.ToString() ?? "DIRECT";
logger.LogInformation("HTTP send {Target} route {Route} account {User}",
target, route, Environment.UserName);
若啟動時對主要目的地各記一次「路徑」與「執行帳戶」,第 8 章的 (1) 到 (3) 只要讀記錄就結束。當有人說「瀏覽器可以,但是……」時,能從應用程式端說「我用了這個設定、這條路徑」,就是對 Proxy 麻煩夠強的應用程式的條件。
flowchart TB
accTitle: 讓 Proxy 可組態並記錄路徑
accDescr: 預設跟隨作業系統設定,允許從應用程式設定指定明確 Proxy URL 或略過或不使用 Proxy,並把實際使用的路徑與執行帳戶一起記錄
def["預設:跟隨作業系統設定"] --> exc{"例外環境?"}
exc -->|"PAC 未讀 / 服務 / 特殊"| cfg["設定 URL、略過或不使用"]
exc -->|"一般情況"| os["使用作業系統預設"]
cfg --> log["記錄路徑與帳戶"]
os --> log
圖 10: 必須時才組態。永遠記錄用了哪條路徑。
10. 總結
- Windows 的 Proxy 設定分成三個系譜──WinINET 每位使用者設定、WinHTTP 電腦設定、環境變數──讀哪一套由應用程式(其 HTTP 堆疊)與執行帳戶決定。
- WinINET 給互動式應用程式,不支援在服務中使用;服務用途是 WinHTTP(
netsh winhttp)的工作。「手動可以、做成服務就不行」先懷疑執行帳戶不同。 netsh winhttp set proxy是靜態設定,不處理 PAC、自動偵測或驗證。在以 PAC 營運的網路上,必須決定無法讀 PAC 的用戶端如何處理。- PAC 的
FindProxyForURL依 URL 回傳 Proxy 或 DIRECT。WPAD 只在有 DHCP/DNS 安排的網路上運作。 - .NET Framework 的預設是執行帳戶的網際網路選項(可用
defaultProxy覆寫);.NET(Core 以後)是環境變數然後使用者 Proxy 設定。明確指定(HttpClientHandler.Proxy)永遠最高優先。 - 407 是 Proxy 驗證錯誤;以服務帳戶執行的應用程式裡,典型原因是「預設認證」變成另一個人。
- TLS 檢查 Proxy 以發佈內部 CA 憑證為前提,憑證錯誤的正確答案是發佈到憑證存放區,不是關掉驗證。釘選流量需要排除。
- 依「重現 → 收集三個系譜的設定 → 識別執行帳戶 → 分類錯誤 → Proxy 記錄」的順序機械隔離。應用程式端,「能設定 Proxy,並記錄它用的路徑」的設計是最好的預防。
下次有人來諮詢「只有業務應用程式連不上」時,先問這個。
那個應用程式以誰的帳戶執行,它讀的是 Proxy 設定三個系譜裡的哪一個?
這一問會大幅改變調查的入口。
相關文章
- 不要用 using 包住 HttpClient ── C# 商用應用程式的 HTTP 通訊實務(生成模式・逾時・重試)
- Windows 的封包擷取實務 ── 在 pktmon、netsh trace 與 Wireshark 之間選擇
- Windows 防火牆與業務應用程式 ── 受信規則要在安裝程式中登錄
- Windows 服務的建立與維運 ── 從工作排程器的取捨到 BackgroundService 服務化
- 圖解NTLM與Kerberos ── 為什麼驗證會「回退」到NTLM
- Windows 憑證存放區實務指南 ── 該放進使用者,還是電腦?
相關諮詢領域
小村軟體有限公司承接企業內部 Proxy、需驗證 Proxy 與 TLS 檢查環境下 Windows 應用程式通訊障礙的調查──「開發機可以、客戶網路不能通訊」、「做成服務後就到不了外部 API」──以及預設有 Proxy 環境的業務應用程式通訊設計(設定項目、逾時、記錄設計)諮詢。從整理重現步驟與如何收集記錄開始也可以。
參考連結
-
Microsoft Learn, WinINet vs. WinHTTP. 關於除非在服務或需要模擬與工作階段隔離的處理程序中,否則使用 WinINET 的指引,以及涵蓋認證快取、認證提示、服務支援、模擬、工作階段隔離等的功能比較表。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, netsh winhttp. 關於 netsh winhttp show/set/import/reset 的語法;set proxy 的 proxy-server 與 bypass-list;import proxy source=ie;以及經 set advproxy 以 JSON 形式設定詳細 Proxy(Proxy、ProxyBypass、AutoconfigUrl、AutoDetect)。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, About WinHTTP. 關於 WinHTTP 是為服務與伺服器端用途設計的 HTTP 堆疊,支援以服務帳戶執行與模擬,且不分享瀏覽器的 Cookie、快取、認證或使用者的網際網路選項。 ↩ ↩2 ↩3
-
Microsoft Learn, Using a proxy with Delivery Optimization. 關於 netsh winhttp set proxy 是不支援自動偵測、PAC URL 或 Proxy 驗證的靜態設定;沒有登入使用者之情境的裝置層級 Proxy 組態(NetworkProxy CSP、「將 Proxy 設定設為電腦層級」原則);以及憑證釘選流量在 TLS 檢查下失敗而需要排除。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
Microsoft Learn, WinHTTP AutoProxy Support. 關於 PAC 指令碼包含依請求計算 Proxy 清單的 FindProxyForURL(url, host) 函式、以特殊回傳值表示直接連線,以及較舊的 AutoProxy API 不會把自動 Proxy 自動整合進 HTTP 堆疊,因此應用程式必須呼叫 WinHttpGetProxyForUrl。 ↩ ↩2 ↩3
-
Microsoft Learn, WinHttpGetProxyForUrl function. 關於它是 WPAD 通訊協定的實作,因為 PAC 檔可依 URL 回傳不同 Proxy 而必須依 URL 呼叫,以及同時支援明確 PAC URL 與從網路自動偵測。 ↩ ↩2
-
Microsoft Learn, HttpClient.DefaultProxy Property. 關於 Windows 先讀 HTTP_PROXY、HTTPS_PROXY、ALL_PROXY、NO_PROXY 環境變數,未定義時讀使用者 Proxy 設定;Linux 在沒有環境變數時以沒有 Proxy 初始化;NO_PROXY 不支援萬用字元並使用前導點子網域比對;以及 Proxy URL 可包含使用者名稱與密碼。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, Configuring Internet Applications. 關於 .NET Framework 上 defaultProxy 元素定義預設 Proxy;沒有 Proxy 屬性的 HttpWebRequest 使用預設 Proxy;以及系統網際網路設定與設定檔設定結合且設定檔端優先。 ↩ ↩2 ↩3
-
Microsoft Learn, defaultProxy element (network settings). 關於 system.net/defaultProxy 元素的 enabled 與 useDefaultCredentials 屬性、proxy、bypasslist 與 module 子元素、元素為空時使用系統 Proxy 設定,以及遷移到 .NET 6 以後時用 HttpClient.DefaultProxy 組態。 ↩ ↩2 ↩3
-
Microsoft Learn, Authentication in WinHTTP. 關於需要 Proxy 驗證時回傳狀態碼 407 與 Proxy-Authenticate 標頭(伺服器驗證是 401 與 WWW-Authenticate);Basic 驗證與 Kerberos 等挑戰/回應配置的差異;以及挑戰/回應配置代表使用者名稱與密碼不走網路。 ↩ ↩2 ↩3
-
Microsoft Learn, HttpClientHandler.DefaultProxyCredentials Property. 關於在 UseProxy 為 true 且 Proxy 為 null 而使用系統預設 Proxy 時,設定用來向該預設 Proxy 驗證之認證的屬性。 ↩ ↩2
-
Microsoft Learn, WebProxy.Credentials Property. 關於 Credentials 屬性是回應 HTTP 407 時送給 Proxy 的認證,以及許多用戶端情境建議將 UseDefaultCredentials 設為 true 以使用已登入使用者的預設認證。 ↩ ↩2
-
Microsoft Learn, Understanding implications when using network intermediation to decrypt or manipulate Microsoft 365 traffic at the network layer. 關於 TLS 檢查(SSL 解密)是 Proxy 或防火牆解密、檢查並重新加密 TLS 的組態;它可能使假設端對端 TLS 的服務故障與效能下降;以及建議將前往 Microsoft 365 的流量從網路層解密與檢查排除。 ↩ ↩2 ↩3
-
Microsoft Learn, Make HTTP requests with the HttpClient class. 關於 HttpClient.DefaultProxy 與 HttpClientHandler.Proxy 兩種組態方法;指定 Proxy 優先於設定檔與本機電腦設定;經 DNS 名稱 wpad 或 DHCP 取得 PAC 檔(wpad.dat 等)的典型 WPAD 組態;以及依平面名稱、迴路與網域後綴比對的本機目的地略過判斷。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, WinHttpOpen function. 關於各 dwAccessType 值的意義。WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY(Windows 8.1 以後)從系統/使用者 Proxy 設定自動決定 Proxy,並自動處理容錯移轉與驗證;WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 自 8.1 起淘汰。 ↩
-
Microsoft Learn, Work with existing on-premises proxy servers. 關於對外 HTTPS 以向 Proxy 的 CONNECT 請求建立;成功回 HTTP 200;以及 407(需要驗證)或 502 等回應表示 Proxy 未允許通訊,因此應與 Proxy 端團隊一起推進隔離。 ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
不要用 using 包住 HttpClient ── C# 商用應用程式的 HTTP 通訊實務(生成模式・逾時・重試)
C# 的 HttpClient 若每次都用 using 生成,會導致 Socket 枯竭;改用 static 又無法跟上 DNS 變更──本文從 Windows 商用應用程式的實務角度,整理這兩大問題的成因,以及透過 PooledConnectionLifetime、IHt...
Windows 封包擷取實務 — pktmon、netsh trace、Wireshark 怎麼選
應用程式紀錄只留下「逾時」的通訊故障,往下一層看實際走過線路的封包就能調查。即使伺服器不能安裝 Wireshark,也能用內建的 pktmon 與 netsh trace 擷取。本文整理擷取與分析分開的現場作法。
多執行緒的實務最佳實踐 .NET篇 ── 在增加執行緒之前先決定的事
針對 .NET/C# 整理能防止「建立執行緒後偶爾當機、卡死」的設計定石。內容涵蓋不自行建立執行緒而改用 Task、減少共享可變狀態、鎖的紀律、以 CancellationToken 設計停止機制,以及 UI 執行緒的處理方式。
在 C#・PowerShell 中使用 WMI/CIM ── 硬體資訊取得・處理程序監控・遠端查詢的實務指南
取得 PC 序號、監控磁碟可用空間、偵測處理程序啟動,這些定番需求的標準答案就是 WMI/CIM。本文解說 Get-CimInstance 等 CIM Cmdlet 的用法、從舊版 Get-WmiObject 的遷移方式、C# 的 System.Management 與 C...
Windows 防火牆與業務應用程式 ── 受信規則要在安裝程式中登錄
「開發機上正常運作,但在客戶端卻無法通訊」的典型原因就是 Windows 防火牆。本文說明受信預設封鎖與設定檔的機制、為何不能把正式環境交給通知對話方塊處理,以及在安裝程式中登錄受信規則與問題排查的步驟。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
故障調查 & 長期運行故障
整理間歇性故障、通訊診斷、長期運行當機、失敗路徑測試基礎的主題頁面。
常見問題
整理諮詢這個主題時常見的問題。
- 瀏覽器連得上,只有業務應用程式過不了企業內部 Proxy。為什麼?
- 瀏覽器讀的是 WinINET 的每位使用者 Proxy 設定,但業務應用程式不一定讀同一套。以 Windows 服務執行、或以其他帳戶執行的應用程式,會查詢該帳戶看得到的設定、WinHTTP 的電腦設定,或環境變數。先確認執行帳戶,再用 netsh winhttp show proxy 與使用者設定兩邊檢查該帳戶看得到的 Proxy 設定。若能在同一台電腦、同一個帳戶下用 curl.exe 等重現,就可把它當成設定系譜之間的不一致,而不是應用程式本身的問題。
- 我設了 netsh winhttp set proxy,應用程式的流量卻沒變。為什麼?
- netsh winhttp 設的是 WinHTTP 的電腦預設值。它不影響讀 WinINET 的瀏覽器或互動式應用程式,也不影響優先讀環境變數的 .NET(Core 以後)HttpClient。netsh winhttp set proxy 也是靜態設定,不處理 PAC 自動設定、自動偵測或 Proxy 驗證。你得先確認目標應用程式用哪一套 HTTP 堆疊、從哪個設定系譜解析 Proxy。
- .NET 應用程式讀哪些 Proxy 設定?
- .NET Framework 預設使用執行帳戶的網際網路選項(等同 WinINET)設定,並可用 app.config 的 system.net/defaultProxy 元素覆寫。Core 以後的 HttpClient 先讀 HTTP_PROXY、HTTPS_PROXY、NO_PROXY 等環境變數,未定義時才回落到 Windows 使用者 Proxy 設定。兩種情況都是明確的 HttpClientHandler.Proxy 優先。Framework 與 Core 以後的預設解析順序不同,遷移時必須重新檢查 Proxy 行為。
- 收到 407 Proxy Authentication Required 時該查什麼?
- 407 表示 Proxy 本身在要求驗證,與目的地伺服器的驗證錯誤(401)是兩件事。先從 Proxy-Authenticate 標頭確認 Proxy 要求的驗證配置(Negotiate、NTLM、Basic),並在 .NET 用 HttpClientHandler.DefaultProxyCredentials 或 WebProxy.UseDefaultCredentials 傳入認證。以服務帳戶執行的應用程式裡,「預設認證」會變成該服務帳戶的認證,因此常見事故是互動式使用者沒問題,一做成服務就 407。也請查 Proxy 端記錄它驗證成誰。
- TLS 檢查 Proxy 造成憑證錯誤。可以關掉憑證驗證嗎?
- 不建議關掉。TLS 檢查 Proxy 會解密流量,再把以自己 CA 重新簽署的憑證呈現給用戶端,若該 CA 憑證不在信任的根憑證中,驗證就會失敗。正確作法是把內部 CA 憑證發佈到 Windows 憑證存放區(通常是本機電腦的「受信任的根憑證授權單位」)。在程式裡關掉驗證,代表應用程式拿到外部網路時無法偵測中間人攻擊,漏洞會一直留下。