企業內部 Proxy 與 Windows 應用程式 ── 整理 WinINET、WinHTTP、.NET 的 Proxy 解析

· · Windows, Proxy, WinHTTP, WinINET, .NET, HttpClient, PAC, WPAD, 網路

更新紀錄(僅初版,2026年08月20日 發布)
初次發布
引用本文(DOI(已登錄存檔): 10.5281/zenodo.22176242)

以下 DOI 指向先前登錄的存檔,內容可能與目前正文不同。引用目前正文時,請使用本頁網址。

Go Komura(2026)。〈企業內部 Proxy 與 Windows 應用程式 ── 整理 WinINET、WinHTTP、.NET 的 Proxy 解析〉。小村軟體有限公司。 https://comcomponent.com/zh-TW/blog/windows-proxy-wininet-winhttp-dotnet/

DOI(已登錄存檔)
10.5281/zenodo.22176242
DOI(上次登錄版本)
10.5281/zenodo.22176243

「瀏覽器開得了外部網站,只有業務應用程式連不上外部 API」「手動執行沒問題,一做成 Windows 服務就失敗」。在有企業內部 Proxy 的環境裡,這類落差很常發生。

調查的起點,是那個應用程式以誰的帳戶執行、讀取的又是哪一套 Proxy 設定。Windows 的 Proxy 設定不只一套。瀏覽器、服務、.NET 的 HttpClient,有可能各自參照不同的設定。

原因大多不是 Proxy 伺服器故障,也不是應用程式有錯,而是設定系統與執行帳戶之間的落差。本文面向中小企業的 IT 人員與 Windows 應用程式開發者,依序整理設定的全貌、PAC、驗證、憑證,一直到釐清原因的步驟。

遇到的狀況、想知道的事 先讀這一節
只有瀏覽器連得上/設定改了行為卻沒變 三套設定系統
手動執行沒問題,做成服務就失敗 執行帳戶的差異
只有特定 URL 不通/PAC 的設定沒被採用 PAC 與 WPAD
從 .NET Framework 移到 .NET 之後行為變了 .NET 的解析順序
收到 407 Proxy 驗證
出現憑證錯誤 TLS 檢查
不知道該從哪裡查起 五個步驟的疑難排解

HttpClient 的建立模式與逾時設計本身,在「不要用 using 包住 HttpClient」中說明。本文的重點是:從哪一套設定解析出 Proxy,以及之後通訊又在哪一步停住。

1. 先講結論

最先要掌握的是以下三點。

  • 看設定之前,先確定應用程式與執行帳戶。WinINET 的每位使用者設定、WinHTTP 的電腦設定、環境變數,讀哪一套由應用程式端決定。管理員在自己畫面上看到的設定,服務未必看得到。123
  • 不要用連得上的 URL,要用出問題的 URL 來查。PAC 會依 URL 分別回傳 Proxy 或 DIRECT。在 .NET 中,執行階段的差異、環境變數、處理常式上的明確指定,也都會改變路徑。456
  • 把路徑、驗證、憑證分開來查。407 是 Proxy 驗證,與目的地的 401 是兩回事。TLS 檢查造成的憑證錯誤,不是靠停用驗證解決,而是靠發佈企業內部 CA 與設定必要的排除項來處理。783
Proxy 調查時要對齊的資訊先確定應用程式的 HTTP 堆疊與執行帳戶,對齊該帳戶看得到的設定與出問題的 URL,再分別檢查路徑、驗證與憑證HTTP 堆疊與帳戶確認該帳戶的設定用出問題的 URL 確認路徑驗證與憑證也分開查

圖1:先對齊「哪一套設定」「從誰的角度看到的設定」「哪一個 URL」,再去查失敗發生在哪裡。

用一句話總結就是:每次說「我確認過 Proxy 設定了」的時候,都要能說清楚看的是三套系統中的哪一套、又是從哪個帳戶的角度看的。

圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 19 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle

2. Windows 的「Proxy 設定」有三套系統

Windows 上的應用程式要找到企業內部 Proxy,路徑大致分成三套系統。

設定系統 設定位置、命令 範圍 主要由誰讀取
① WinINET(網際網路選項) 設定應用程式→網路和網際網路→Proxy,inetcpl.cpl 每位使用者(預設) 瀏覽器、互動式桌面應用程式、.NET Framework 的預設
② WinHTTP(電腦設定) netsh winhttp set proxy / set advproxy 電腦 Windows 服務、部分作業系統元件
③ 環境變數 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY 處理程序(依定義位置繼承) .NET(Core 以後)的 HttpClient、curl、Node.js 與 Python 等跨平台工具

「Windows 的 Proxy 設定」通常指的是①

設定應用程式裡看到的「Proxy」,從歷史上說就是 Internet Explorer 的網際網路選項,也就是 WinINET 的組態。預設會依使用者分別儲存。3

②是為服務這類「沒有已登入使用者的情境」所準備的電腦層級預設值。③主要是源自跨平台世界的工具慣例,在 Windows 上,.NET(Core 以後)與 curl 等也會讀取。5

讀哪一套由應用程式決定,而不是由設定的人決定

使用 WinINET 的應用程式讀①,使用 WinHTTP 的讀②或應用程式自己的指定,.NET(Core 以後)則是③→①,參照對象都是固定的。

因此,遇到「設定明明是對的卻連不上」時,要先確認你檢查的設定與應用程式實際讀取的設定是不是同一套系統。在把三套系統全部改成同一個值之前,先確定目標應用程式的入口更重要。

也有不依使用者、改依裝置設定的做法

啟用群組原則「依電腦而非依使用者進行 Proxy 設定」之後,就能把①切換成電腦層級,讓所有使用者套用同一份設定。使用 MDM(如 Intune)時,可以用 NetworkProxy CSP 依裝置設定。3

改變每位使用者設定的適用範圍WinINET 的設定預設是每位使用者,但可以用群組原則切換成依電腦的設定,MDM 則可以用 NetworkProxy CSP 依裝置設定WinINET 設定預設依使用者用 GPO 改為依電腦對所有使用者套用同一設定MDM 的 NetworkProxy CSP依裝置設定

圖2:①的設定範圍也可以改成依裝置,所以「每位使用者」只是預設狀態。

3. WinINET 與 WinHTTP ── 一個給互動式應用程式,一個給服務

3.1. 職責的差異

WinINET 與 WinHTTP 都是 Windows 內建的 HTTP 用戶端堆疊,不過它們設想的執行型態不同。

WinINET 面向互動式桌面應用程式。它會自動沿用使用者的網際網路選項、Proxy、Cookie 與認證快取,必要時還能顯示輸入認證的 UI。另一方面,它不支援在服務或類似服務的處理程序中使用。1

WinHTTP 面向服務與伺服器端。它支援以服務帳戶執行、執行緒模擬(impersonation)與工作階段隔離。代價是不共用瀏覽器的設定、Cookie 與認證,也不顯示 UI。2

Microsoft 給出的取捨指引同樣是:只要不是在服務內部執行,也不是需要工作階段隔離或模擬的類服務處理程序,就用 WinINET;是服務就用 WinHTTP。1 不過,這並不表示所有以服務方式執行的應用程式都會讀取 WinHTTP 的電腦設定。.NET 寫成的服務,放在 3.3 節另外說明。

3.2. netsh winhttp 的基本操作

WinHTTP 的電腦預設 Proxy 用 netsh 操作。9

:: 顯示目前的 WinHTTP Proxy 設定
netsh winhttp show proxy

:: 設定靜態 Proxy(附略過清單)
netsh winhttp set proxy proxy-server="proxy.example.co.jp:8080" bypass-list="*.example.co.jp;<local>"

:: 匯入網際網路選項(WinINET)的設定
netsh winhttp import proxy source=ie

:: 還原為預設值(DIRECT)
netsh winhttp reset proxy

不要混淆靜態設定、匯入與自動設定

set proxy 是靜態設定,不處理自動偵測、PAC URL 的指定與 Proxy 驗證。3

import proxy source=ie 只是把執行當下的靜態設定複製過來。之後再修改網際網路選項,WinHTTP 這一側不會跟著變。

Proxy 設定的匯入只是一次性複製import proxy source=ie 只是把當下的靜態設定複製到 WinHTTP,之後修改網際網路選項也不會自動跟隨執行當下的靜態設定import proxy source=ie複製到 WinHTTP日後的變更不會自動跟隨

圖3:import 不是同步設定,而是把當下的靜態設定匯入進來的操作。

若要連 PAC 與 WPAD 的自動偵測都依電腦設定,就用 netsh winhttp set advproxy。JSON 形式的詳細設定項目有 Proxy、ProxyBypass、AutoconfigUrl、AutoDetect。9

3.3. 最常見的陷阱:服務不會讀取使用者的 IE 設定

典型的例子是:開發者把在自己電腦上執行的工具,改成以 LocalSystem 執行的 Windows 服務。

開發時用自己的每位使用者設定(①)能通訊,但 LocalSystem 看得到的是另一套設定。如果 WinHTTP 的電腦設定未設定(DIRECT),它就會嘗試直連外部 API 而逾時。做成服務本身的步驟,在「Windows 服務的建立與維運」中說明。

從手動執行改成服務後的落差原本在開發者的每位使用者設定下能運作的工具,一旦改成 LocalSystem 的服務,看得到的設定就變了,若 WinHTTP 的預設值是 DIRECT 就會嘗試直連而失敗以開發者身分手動執行用自己的設定能通訊改為 LocalSystem 的服務看得到的設定變了WinHTTP 未設定就直連連外部 API 逾時

圖4:即使是同一台電腦,執行帳戶一變,能參照到的設定也會變。

設定要依那套 HTTP 堆疊讀取的形式來準備

對於沒有使用者登入也要通訊的處理程序,要準備電腦層級的設定。不過設定方式要配合 HTTP 堆疊。

對象 設定的準備方式
使用 WinHTTP 的原生應用程式與 Windows 元件 準備 netsh 的 WinHTTP 設定3
使用 .NET(Core 以後)HttpClient 的服務 用系統環境變數(如 HTTPS_PROXY),或從應用程式設定明確指定 HttpClientHandler.Proxy

.NET(Core 以後)的 HttpClient 不會讀取 WinHTTP 的電腦設定。請不要因為「它是服務,所以設定 netsh 就好」而下判斷。詳細的優先順序在第 5 章確認。

對外帶電腦寫死靜態設定,會在反方向引發問題

如果在筆記型電腦上固定寫入企業內部的靜態 Proxy,到了公司外面就到不了那個 Proxy,也就無法通訊。電腦層級的靜態設定,應當視為適用於網路組態不會改變的伺服器的手段。3

4. PAC 與 WPAD ── 「自動設定」的內容

PAC 負責計算路徑,WPAD 負責找出 PAC 的位置。把「自動設定」拆成這兩件事,就看得出該確認哪裡。

4.1. PAC 檔案與 FindProxyForURL

PAC(Proxy Auto-Configuration)是用 JavaScript(ECMAScript)撰寫的檔案。其中必備的 FindProxyForURL(url, host) 函式,會針對指定的 URL 與主機,回傳要使用的 Proxy 清單,或表示直接連線的 DIRECT。10

function FindProxyForURL(url, host) {
    // 企業內部網域與私有位址直連
    if (dnsDomainIs(host, ".example.co.jp") ||
        isInNet(host, "10.0.0.0", "255.0.0.0")) {
        return "DIRECT";
    }
    // 其餘經由 Proxy。第一個不可用就退回到下一個
    return "PROXY proxy1.example.co.jp:8080; PROXY proxy2.example.co.jp:8080; DIRECT";
}

在這個例子裡,企業內部網域與指定的私有位址直連,其餘則經由 Proxy。後者在第一個 Proxy 不可用時前進到下一個,最後退回到 DIRECT。

PAC 會依目的地改變路徑文中所列的 PAC 範例會判斷 URL 與主機,若是企業內部網域或指定的私有位址就回傳 DIRECT,否則回傳帶順序的 Proxy 清單是否傳入 URL 與主機符合範例的內部條件?回傳 DIRECTProxy 1、2、DIRECT 的順序

圖5:PAC 的答案會隨 URL 而變,所以別的網站成功,並不能驗證出問題的 API 走的是哪條路徑。

由此引出兩點調查上的注意事項。

要傳入出問題的那個 URL 來確認。PAC 對不同 URL 可能給出不同的答案。WinHTTP 的自動 Proxy 功能,同樣被設計成每次都傳入請求對象的 URL 去查詢。「瀏覽器上別的網站看得到」並不能證明出問題的 API 走的是同一條路徑。4

Proxy 記錄裡沒有的通訊,也要懷疑 DIRECT 與略過。DIRECT 的意思是「不要走 Proxy,直接去」。發往企業內部的通訊沒有出現在記錄裡時,就確認是不是命中了 PAC 的 DIRECT 判定或略過清單。

4.2. 以 WPAD 進行自動偵測

開啟「自動偵測設定」後,系統會用 WPAD(Web Proxy Auto-Discovery)尋找 PAC 檔案的位置。一般的做法是用 DHCP 發送 PAC URL,或用 DNS 解析名為 wpad 的主機,再從 http://wpad/wpad.dat 這樣的 URL 取得。11

從自動偵測到取得 PACWPAD 會用 DHCP 或 DNS 尋找 PAC 檔案的位置,從該位置取得 PAC 並用於計算路徑啟用自動偵測用 DHCP 或 DNS 尋找位置取得 PAC 檔案依目的地計算路徑沒有相應機制就偵測失敗

圖6:自動偵測需要網路端以 DHCP/DNS 事先備好機制。

在沒有這套機制的網路裡只開啟自動偵測並不會生效,反而會增加等到偵測失敗為止的時間。選了「自動」並不等於到哪裡都解析得出來。

4.3. 讀不了 PAC 的用戶端的行為

即使發佈了 PAC,也不是所有用戶端都會去求值。netsh winhttp set proxy 是靜態設定,而採用 HTTP_PROXY 環境變數方式的工具,原則上也沒有地方可以寫 PAC URL,只能指定固定的 Proxy URL。35

對於直接使用 WinHTTP 的原生應用程式,要確認工作階段的開啟方式。

WinHttpOpen 的用法 自動 Proxy 的處理
在 Windows 8.1 以後指定 WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY WinHTTP 會依系統/使用者的設定(含 WPAD 與 PAC)對每個請求自動解析12
指定傳統的 WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 等 需要應用程式自己呼叫 WinHttpGetProxyForUrl,再把結果設定到請求上。DEFAULT_PROXY 在 8.1 以後已不建議使用1012
確認 WinHTTP 是否會用到 PAC若 WinHTTP 的工作階段是以 AUTOMATIC_PROXY 開啟就會自動解析,而用傳統方式開啟時需要應用程式自己呼叫 AutoProxy API 並設定結果是傳統的開啟方式確認 WinHttpOpen 的指定是否為 AUTOMATIC_PROXY?WinHTTP 自動解析應用程式呼叫 AutoProxy API把結果設定到請求上

圖7:只知道「它用的是 WinHTTP」,並不能判斷 PAC 也會自動生效。

在較舊的實作裡,即使有 PAC 也可能不會被使用。在以 PAC 維運的網路中,也要事先決定要用靜態設定或環境變數,發給讀不了 PAC 的用戶端什麼內容。

5. .NET 的 Proxy 解析 ── Framework 與 Core 以後是兩回事

.NET Framework 與 .NET(Core 以後)決定預設 Proxy 的方式並不相同。用 Framework 時代的知識去查 .NET 8 的應用程式,可能會查錯設定。

5.1. .NET Framework ── 預設是網際網路選項,用 defaultProxy 覆寫

.NET Framework 的 HttpWebRequest,以及建構在它之上的 HttpClient,只要不明確指定 Proxy 就會使用預設 Proxy。它由執行中帳戶的網際網路設定(相當於 WinINET)與組態檔組合決定,其中組態檔的設定優先。6

用 app.config 或 machine.config 的 system.net/defaultProxy 控制。13

<configuration>
  <system.net>
    <!-- useDefaultCredentials: 是否向需要驗證的 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 替換同一份預設值。136

.NET Framework 的預設 Proxy.NET Framework 以執行中帳戶的網際網路設定為基礎,並優先採用組態檔指定的值來決定預設 Proxy執行帳戶的設定優先採用組態檔的指定Framework 的預設 Proxy用 DefaultWebProxy 替換

圖8:Framework 的預設值取自「執行中帳戶」的設定,未必是管理員畫面上看到的那一份。

以服務帳戶執行時,需要和 3.3 節一樣的注意。預設讀取的是該帳戶的網際網路選項,與管理員桌面上看到的是不同的一份,而且通常是空的。

5.2. .NET(Core 以後)── 先看環境變數,再看作業系統的使用者設定

.NET(Core 以後)有一個靜態屬性 HttpClient.DefaultProxy。它是處理常式未明確指定時所用的預設值,在 Windows 上按照先讀環境變數,未定義時再讀使用者的 Proxy 設定的順序初始化。5

環境變數 意義
HTTP_PROXY HTTP 請求所用的 Proxy
HTTPS_PROXY HTTPS 請求所用的 Proxy
ALL_PROXY 上述變數未定義時的退回項
NO_PROXY 不使用 Proxy 的主機清單,以逗號分隔

把指定 Proxy 的三個變數與 NO_PROXY 分開看

只要 HTTP_PROXY、HTTPS_PROXY、ALL_PROXY 之中定義了任何一個,就會優先於作業系統端的設定。如果測試用的 HTTPS_PROXY 忘了刪,或是 CI/CD 的範本注入了它,實際路徑就會與畫面上看到的設定不同。

另一方面,只定義 NO_PROXY 並不會構成由環境變數指定的 Proxy。在 Windows 上仍會繼續使用作業系統的使用者 Proxy 設定。

Windows 上 .NET 預設 Proxy 的初始化只要 HTTP_PROXY、HTTPS_PROXY、ALL_PROXY 之中定義了任何一個就優先使用環境變數,都未定義時則使用 Windows 的使用者設定。只有 NO_PROXY 不會構成由環境變數指定的 Proxy是否預設 Proxy 的初始化有指定 Proxy 的三個變數?優先使用環境變數Windows 的使用者設定只有 NO_PROXY 不構成設定

圖9:先確認是否存在由環境變數指定的 Proxy,並與只設了 NO_PROXY 的狀態區分開。

NO_PROXY 的「開頭那個點」與在 Linux 上的差異

NO_PROXY 不支援萬用字元(*)。開頭加上點的 .example.com 可以比對到 www.example.com,卻比對不到 example.com 本身。5

在 Linux 容器等環境中,環境變數未定義時會以「不使用 Proxy」初始化。Windows 與 Linux 的預設行為不同,因此移到容器時也要確認。5

5.3. 明確指定 ── HttpClientHandler.Proxy 與 UseProxy

無論哪一種執行階段,對 HttpClientHandler.Proxy 的明確指定都擁有最高優先權,優先於作業系統設定與組態檔。UseProxy = false 時完全不使用 Proxy。11

using System.Net;

// 明確使用從應用程式設定讀到的 Proxy
var handler = new HttpClientHandler
{
    Proxy = new WebProxy("http://proxy.example.co.jp:8080")
    {
        BypassProxyOnLocal = true,
        BypassList = new[] { @"^intra\.example\.co\.jp$" },
        UseDefaultCredentials = true // 遇到需要驗證的 Proxy 時,用執行帳戶的認證回應
    },
    UseProxy = true
};
var client = new HttpClient(handler);

// 完全不使用 Proxy 的用戶端(直連企業內部 API 用)
var directHandler = new HttpClientHandler { UseProxy = false };
var directClient = new HttpClient(directHandler);
從處理常式決定實際生效的 ProxyUseProxy 為 false 就直連,為 true 則使用處理常式上對 Proxy 的明確指定,沒有明確指定時使用預設 Proxy否是是否UseProxy 為 true?直連有明確指定 Proxy?使用指定的 Proxy使用預設 Proxy

圖10:在查預設 Proxy 之前,先確認處理常式上的明確指定與 UseProxy。

也要注意發往本機時的自動略過

在沒有明確指定、依循作業系統設定的情況下,不含點的扁平名稱、回送位址、與本機網域尾碼相符的目的地等,可能會被視為「本機」而被略過。11

遇到「直接寫 IP 位址與寫名稱的行為不同」「改成 FQDN 之後開始走 Proxy 了」時,就確認這個判定。

把到這裡為止的優先順序整理起來,如下所示。

優先順序(高→低) .NET Framework .NET(Core 以後)
1 HttpClientHandler.Proxy 等的明確指定 同左
2 app.config 的 defaultProxy 對 HttpClient.DefaultProxy 指派
3 執行帳戶的網際網路選項 環境變數(如 HTTP_PROXY)
4 ― Windows 的使用者 Proxy 設定

6. 需要驗證的 Proxy ── 407 是「Proxy 的」驗證錯誤

6.1. 不要混淆 407 與 401

即使到達了 Proxy,過不了驗證也無法繼續。要求驗證的是哪一方,用狀態碼與標頭來區分。7

回應 要求驗證的一方 要確認的標頭
407 Proxy Authentication Required Proxy Proxy-Authenticate
401 目的地伺服器 WWW-Authenticate

遇到 407 時,先確認 Proxy-Authenticate 中列出的配置。Basic 是送出使用者名稱與密碼的配置,Negotiate(Kerberos/NTLM)等則是挑戰/回應配置。後者不會讓密碼本身走上網路,而是經過多次往返完成驗證。7

對需要驗證的 Proxy 回應的流程用戶端從 Proxy 收到 407 與驗證配置的通知,再依該配置用對應的認證回應。挑戰回應配置需要多次往返Proxy用戶端Proxy用戶端依配置可能需要多次往返要求通訊407 與 Proxy-Authenticate依配置回應驗證

圖11:遇到 407 時,要確認的是送給 Proxy 的驗證資訊,而不是送給目的地伺服器的。

會「退回」到哪一種驗證配置,在「圖解 NTLM 與 Kerberos」中有詳細說明。

6.2. 在 .NET 中傳遞認證的方式

依循預設 Proxy 與明確指定 Proxy,設定的位置並不相同。

使用預設 Proxy 並需要傳遞驗證資訊時,用 HttpClientHandler.DefaultProxyCredentials。它是在 UseProxy = true 且 Proxy = null 時,送往該預設 Proxy 的認證。14

using System.Net;

var handler = new HttpClientHandler
{
    UseProxy = true,   // 預設值。與 Proxy = null 組合時使用系統預設的 Proxy
    Proxy = null,
    // 用執行帳戶(登入中使用者或服務帳戶)的認證回應 407
    DefaultProxyCredentials = CredentialCache.DefaultCredentials
};
var client = new HttpClient(handler);

明確指定 Proxy 時,把認證放在 WebProxy 這一側。在多數用戶端情境中,建議使用登入中使用者的預設認證,而不是個別的使用者名稱與密碼,對應的寫法就是 WebProxy.UseDefaultCredentials = true。15

6.3. 服務帳戶的 407 問題

「預設認證」指的是執行該處理程序的帳戶的認證。互動式使用者會以該使用者驗證,LocalSystem 的服務則以電腦帳戶驗證。

做成服務後被驗證的主體會改變使用預設認證時,互動式執行是以該使用者驗證,LocalSystem 的服務則以電腦帳戶驗證,因此要確認 Proxy 能不能驗證這個主體互動式使用者LocalSystem執行帳戶是什麼?該使用者的認證電腦帳戶確認 Proxy 能否完成驗證

圖12:即使程式碼裡一直選的是「預設」,做成服務後 Proxy 所驗證的主體也會改變。

在與 AD 整合、以使用者驗證的 Proxy 上,電腦帳戶或本機帳戶可能無法通過驗證,407 會一直持續。反過來,也有環境依來源 IP 或依帳戶為服務設了免驗證的例外。

因此,要做成服務的應用程式,應該在設計階段就決定:是使用網域的服務帳戶(如 gMSA),還是在 Proxy 端設免驗證的例外,或者準備一個免驗證的內部中繼 Proxy。調查 407 需要同時看應用程式的設定,以及「Proxy 能不能驗證這個執行帳戶」。

也可以像 HTTP_PROXY=http://user:pass@proxy:8080 這樣把認證嵌進環境變數。5 不過明文密碼會暴露在環境變數、也就是處理程序資訊裡,不建議作為長期維運手段。

7. HTTPS 與 Proxy ── CONNECT 通道與 TLS 檢查

7.1. HTTPS 以「通道」的方式穿過 Proxy

HTTPS 走 Proxy 時,用戶端會先送出 CONNECT 目的地主機:443,打通一條 TCP 通道。成功時 Proxy 回傳 200,之後用戶端與目的地伺服器在通道內進行 TLS 交握。通道沒打通時會回傳 407 或 502 等。16

HTTPS 通道打通之前HTTPS 會用 CONNECT 請求開啟 TCP 通道,回傳 200 之後在通道內進行 TLS 交握。通道沒有打通時會回傳 407 或 502 等成功、200失敗用 CONNECT 指定目的地通道打通了?在通道內建立 TLS 連線407 或 502 等

圖13:把開啟通道階段的失敗,與之後 TLS 連線的失敗分開來看。

在這種「直通型」之下,Proxy 讀不到加密後的 HTTPS 內容。記錄裡看得到的只有目的地主機名稱與連線成敗,看不到 URL 路徑。

7.2. TLS 檢查型 Proxy 與憑證錯誤

TLS 檢查(SSL 解密、break and inspect)型的做法是:Proxy 先把 TLS 終結,解密、檢查之後再重新加密。提示給用戶端的憑證,也被換成用 Proxy 自己的 CA 重新簽署的憑證。8

TLS 檢查會換掉憑證TLS 檢查中 Proxy 會終結 TLS 並檢查內容,向用戶端提示以自己的 CA 重新簽署的憑證,因此必須信任該 CAProxy 終結 TLS解密、檢查、重新加密用企業內部 CA 重新簽署的憑證用戶端這一側必須信任該 CA

圖14:在檢查型之下,問題不只是目的地的憑證,還在於能不能信任企業內部 CA。

先確認是用哪個信任存放區在驗證

這種架構要求把 Proxy 的 CA 憑證發佈到所有用戶端的受信任的根。不只是尚未發佈的電腦,使用自帶信任存放區、不看 Windows 憑證存放區的執行階段,同樣會出現憑證驗證錯誤。

在 .NET 中,它通常表現為 HttpRequestException 以及其內部的 AuthenticationException。要確認其中類似「遠端憑證無效」的訊息。

處理方式是發佈 CA,而不是停用驗證

企業內部 CA 憑證通常發佈到本機電腦的「受信任的根憑證授權單位」。與使用者存放區之間如何取捨,請參考「Windows 憑證存放區實務指南」。

不要採用讓 ServerCertificateCustomValidationCallback 一律回傳 true 這類規避手段。它會成為一個在企業外部網路使用時也無法偵測中間人攻擊的漏洞,長期留存下來。

使用憑證釘選的通訊,要排除在檢查之外

使用憑證釘選的通訊,在 Proxy 換掉憑證的那一刻就會失敗。對於驗證特定 Microsoft 憑證的 Windows 元件等,沒有規避辦法,必須設定排除項。3

區分發佈 CA 與排除憑證釘選的通訊TLS 檢查引發憑證錯誤時要確認對 CA 的信任,而使用憑證釘選的通訊,換掉憑證本身就是失敗原因,因此要從檢查中排除是否憑證驗證錯誤有釘選憑證?從檢查中排除確認參照的信任存放區確認企業內部 CA 的發佈

圖15:發佈企業內部 CA 的處理,與避免憑證被替換的處理是兩回事。

對於發往 Microsoft 365 等 SaaS 的流量,Microsoft 同樣建議將其排除在網路層的解密與檢查之外。8 如果只有特定的雲端服務出現憑證錯誤,就懷疑檢查排除清單與憑證釘選的組合。

8. 疑難排解步驟 ── 用五個步驟找出真兇

在實際調查時,把到目前為止講過的機制,依下列順序確認。

步驟 要做的事 能確定什麼
① 重現 用 curl.exe -v 或 Invoke-WebRequest 存取出問題的 URL(盡量在同一台電腦、同一個帳戶下) 是應用程式本身的問題,還是環境的問題
② 採集設定 採集 netsh winhttp show proxy、每位使用者設定、環境變數這三套系統 哪一套系統裡放著什麼
③ 確定帳戶 確定目標應用程式的執行帳戶(是服務、工作排程器,還是其他使用者) 它用哪一套設定、哪一份認證在執行
④ 錯誤分類 區分 407 / 403 / 名稱解析失敗 / 逾時 / 憑證錯誤 區分 Proxy 驗證、原則拒絕、路徑與 TLS 檢查
⑤ Proxy 記錄 在 Proxy 伺服器的存取記錄中確認對應時刻 通訊究竟有沒有到達 Proxy、又被驗證成了誰

① 重現:即使 URL 相同,也要對齊工具的設定系統

盡量從同一台電腦、同一個帳戶存取出問題的 URL。也要注意所用工具的差異。

工具 調查時要留意的點
Windows 內建的 curl.exe 可以用 -x http://proxy:8080 明確指定 Proxy。TLS 驗證通常使用作業系統的憑證存放區(Schannel)
Windows PowerShell 5.1 的 Invoke-WebRequest 走 .NET Framework 這一側的解析。預設是網際網路選項
PowerShell 7 的 Invoke-WebRequest 走 .NET 這一側的解析。優先使用環境變數

「curl 通得過,應用程式卻不通」是設定系統落差的線索。不能只因為另一個工具成功,就判斷應用程式也走了同一條路徑。

② 採集設定:把三套系統一併記錄下來

在 PowerShell 中可以這樣採集。每位使用者設定所在的 HKCU,是執行這道命令的那個帳戶的。

# ① 每位使用者(WinINET)設定 ── 注意讀的是執行帳戶的 HKCU
Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' |
    Select-Object ProxyEnable, ProxyServer, ProxyOverride, AutoConfigURL

# ② 電腦(WinHTTP)設定
netsh winhttp show proxy

# ③ 環境變數
Get-ChildItem env: | Where-Object Name -match 'proxy'

③ 確定帳戶:是服務就用同一個帳戶重新確認

確定目標是服務、工作排程器,還是由其他使用者執行。若是服務,就用同一個帳戶重做①的重現與②的採集。在管理員自己的工作階段裡成功,並不能證明 LocalSystem 看到的樣子。

對齊執行帳戶之後再重現在管理員工作階段採集到的結果不能證明服務看得到的設定,因此要先確定目標的執行帳戶,再用該帳戶重新確認重現與設定採集在管理員工作階段確認確定目標的執行帳戶用同一帳戶重現、採集核對設定與認證

圖16:確認①②採到的資訊,是不是從③確定的目標帳戶角度看到的資訊。

④ 錯誤分類:把「連不上」拆開

遇到 407 就看第 6 章的 Proxy 驗證,遇到憑證錯誤就看第 7 章的 TLS 檢查。遇到逾時,則把「沒能到達 Proxy」當成第一候選,檢查路徑、名稱解析與防火牆。403 的原則拒絕也要與驗證和逾時分開。

依錯誤區分調查方向把通訊失敗分成 407、403、逾時或名稱解析失敗、憑證錯誤,分別檢查驗證、原則拒絕、路徑與 TLS 檢查通訊的失敗407 就查 Proxy 驗證403 就查原則拒絕逾時、名稱解析就查路徑憑證錯誤就查 TLS

圖17:把狀態碼與例外分開,就能縮小要確認的設定與記錄範圍。

原因不在 Proxy 而在 Windows 防火牆傳入規則的情形,在「Windows 防火牆與業務應用程式」中說明。

⑤ Proxy 記錄:確認是否到達,以及被驗證成哪個帳戶

在對應時刻的存取記錄裡,確認通訊是否到達了 Proxy、又被驗證成了誰。如果沒有任何痕跡,就當成通訊沒有送達 Proxy,去查 PAC 的 DIRECT、略過清單,以及忘了刪除的環境變數。

必要時用封包擷取確認實際的目的地。採集方法請參考「Windows 的封包擷取實務 ── 在 pktmon、netsh trace 與 Wireshark 之間選擇」。

9. 設計上的建議 ── 把應用程式做成「Proxy 可設定」的應用程式

調查的難易度,取決於應用程式的設定項目與記錄。要交付到有企業內部 Proxy 的環境的應用程式,請準備好以下四點。

除了依循預設值,也要能選擇明確指定與直連

預設設為「依循作業系統設定」,並在讀不了 PAC、以服務執行、架構特殊等情況下,允許從設定檔指定 Proxy URL、略過清單與「不使用 Proxy」。實作點就是 5.3 節的 HttpClientHandler.Proxy 與 UseProxy。11

.NET(Core 以後)的預設行為裡,還有 5.2 節說明的環境變數優先。即使交給預設值處理,也要依照那套規則去判斷實際會使用哪一份設定。

把發往企業內部的例外,寫成能放進導入手冊的形式

把發往 API、資料庫、授權伺服器等企業內部目的地的通訊,究竟要用 PAC 的 DIRECT、略過清單還是 NO_PROXY 來排除,明確寫下來。NO_PROXY 不支援萬用字元,以及開頭那個點的意義,要配上範例說明。5

逾時與重試也要以經過 Proxy 為前提

面對 Proxy 停機或等待驗證,一直等待冗長的預設逾時會讓 UI 與維運一起凍住。把連線逾時單獨設短一些,重試只限於冪等的請求。詳情在「不要用 using 包住 HttpClient」中說明。

從實際設定好的處理常式,把路徑記錄下來

如果只把 HttpClient.DefaultProxy 寫進記錄,就會漏掉處理常式上的明確指定與 UseProxy = false,記下與實際不符的路徑。重要的是從建立 HttpClient 所用的那個處理常式中挑出實際生效的設定,並把目的地的略過判定也算進去。

using System.Net.Http;

// handler 與建立 HttpClient 時使用的是同一個執行個體
// UseProxy=false 時一律直連。有明確指定就用它,沒有則使用 DefaultProxy
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 傳送 {Target} 路徑 {Route} 執行帳戶 {User}",
    target, route, Environment.UserName);
從通訊用戶端的組態記錄路徑從建立 HttpClient 所用的處理常式挑出實際生效的 Proxy,做目的地的略過判定與 Proxy 解析,再把路徑與執行帳戶記錄下來與建立時相同的處理常式反映 UseProxy 與明確指定目的地的略過判定、解析記錄路徑與帳戶

圖18:不要只看預設 Proxy,要以用戶端自身的組態為基礎來記錄路徑。

在啟動時記錄下主要目的地的路徑與執行帳戶,第 8 章的①~③就更容易追查。當有人說「瀏覽器明明連得上」時,應用程式能說清楚自己的設定與路徑,才是禁得起調查的設計。

10. 小結

調查 Windows 的 Proxy 問題時,要先對齊設定系統、執行帳戶、目標 URL。

WinINET 的每位使用者設定、WinHTTP 的電腦設定與環境變數是彼此獨立的系統。做成服務後,看得到的設定與認證都會變;.NET Framework 與 .NET(Core 以後)的預設順序也不同。只靠 netsh winhttp set proxy,處理不了 PAC、自動偵測與驗證。

PAC 會依 URL 回傳 Proxy 或 DIRECT,WPAD 則需要網路端事先備好機制。路徑決定之後,還要另外確認 407 的 Proxy 驗證,以及 TLS 檢查帶來的憑證驗證。不要停用憑證驗證,而應發佈 CA,並把使用憑證釘選的通訊排除在外。

釐清原因的順序是:重現→採集三套系統的設定→確定執行帳戶→錯誤分類→Proxy 記錄。應用程式這一側要準備好 Proxy 的明確指定與直連、例外設定、逾時與路徑記錄。

下次有人來問「只有業務應用程式連不上」時,請先這樣確認。

那個應用程式以誰的帳戶在執行,讀的又是三套系統中的哪一套 Proxy 設定?

這一問就是調查的入口。

相關文章

相關諮詢領域

小村軟體有限公司承接這樣的諮詢:「開發機上能跑,到客戶網路就無法通訊」「做成服務後連不上外部 API」這類在企業內部 Proxy、需要驗證的 Proxy、TLS 檢查環境下的 Windows 應用程式通訊問題調查,以及以 Proxy 環境為前提的業務應用程式通訊設計(設定項目、逾時、記錄設計)。從整理現象的重現步驟與記錄的採集方法開始也沒問題。

參考連結

  1. Microsoft Learn, WinINet vs. WinHTTP. 關於「只要不是服務、也不是需要模擬或工作階段隔離的處理程序就使用 WinINET」這項取捨指引,以及認證快取、認證提示、服務支援、模擬、工作階段隔離等功能比較表。 ↩ ↩2 ↩3

  2. 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

  4. Microsoft Learn, WinHttpGetProxyForUrl function. 關於它作為 WPAD 通訊協定的實作,因為 PAC 檔案對不同 URL 可能回傳不同的 Proxy,所以需要依 URL 逐一呼叫,以及它同時支援明確指定 PAC URL 與從網路自動偵測。 ↩ ↩2

  5. 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

  6. Microsoft Learn, Configuring Internet Applications. 關於在 .NET Framework 中 defaultProxy 元素定義預設 Proxy、沒有 Proxy 屬性的 HttpWebRequest 會使用預設 Proxy,以及系統的網際網路設定與組態檔的設定會被組合、且以組態檔這一側優先。 ↩ ↩2 ↩3

  7. Microsoft Learn, Authentication in WinHTTP. 關於需要 Proxy 驗證時會回傳狀態碼 407 與 Proxy-Authenticate 標頭(伺服器驗證則是 401 與 WWW-Authenticate)、Basic 驗證與 Kerberos 等挑戰/回應配置的差異,以及挑戰/回應配置中使用者名稱與密碼不會流經網路。 ↩ ↩2 ↩3

  8. 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

  9. Microsoft Learn, netsh winhttp. 關於 netsh winhttp show/set/import/reset 的語法、set proxy 的 proxy-server 與 bypass-list、import proxy source=ie,以及用 set advproxy 以 JSON 形式(Proxy、ProxyBypass、AutoconfigUrl、AutoDetect)進行詳細 Proxy 設定。 ↩ ↩2

  10. Microsoft Learn, WinHTTP AutoProxy Support. 關於 PAC 指令碼包含 FindProxyForURL(url, host) 函式並為每個請求計算 Proxy 清單、可以直接連線時用特定回傳值表示,以及傳統的 AutoProxy API 沒有把自動 Proxy 整合進 HTTP 堆疊、需要應用程式自己呼叫 WinHttpGetProxyForUrl。 ↩ ↩2

  11. Microsoft Learn, Make HTTP requests with the HttpClient class. 關於 HttpClient.DefaultProxy 與 HttpClientHandler.Proxy 這兩種設定方式、Proxy 指定優先於組態檔與本機電腦的設定、WPAD 透過 DNS 的 wpad 名稱或 DHCP 取得 PAC 檔案(如 wpad.dat)的常見架構,以及依扁平名稱、回送位址、網域尾碼相符所做的本機略過判定。 ↩ ↩2 ↩3 ↩4

  12. Microsoft Learn, WinHttpOpen function. 關於 dwAccessType 各個值的意義:WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY(Windows 8.1 以後)會使用系統/使用者的 Proxy 設定自動決定 Proxy,並自動處理容錯移轉與驗證;WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 在 8.1 以後已不建議使用。 ↩ ↩2

  13. Microsoft Learn, defaultProxy element (network settings). 關於 system.net/defaultProxy 元素的 enabled 與 useDefaultCredentials 屬性、proxy 與 bypasslist 與 module 子元素、元素為空時使用系統的 Proxy 設定,以及移轉到 .NET 6 以後時改用 HttpClient.DefaultProxy 設定。 ↩ ↩2

  14. Microsoft Learn, HttpClientHandler.DefaultProxyCredentials Property. 關於該屬性用於在 UseProxy 為 true 且 Proxy 為 null、使用系統預設 Proxy 時,設定向該預設 Proxy 進行驗證所用的認證。 ↩

  15. Microsoft Learn, WebProxy.Credentials Property. 關於 Credentials 屬性是作為對 HTTP 407 的回應而送給 Proxy 的認證,以及在多數用戶端情境中建議把 UseDefaultCredentials 設為 true 以使用登入中使用者的預設認證。 ↩

  16. Microsoft Learn, Work with existing on-premises proxy servers. 關於輸出的 HTTPS 通訊是透過向 Proxy 送出 CONNECT 請求來建立,成功時回傳 HTTP 200,而 407(要求驗證)或 502 等回應表示 Proxy 沒有放行該通訊,應當與 Proxy 端團隊一起繼續釐清原因。 ↩

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

瀏覽器連得上,為什麼只有業務應用程式過不了企業內部 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 元素覆寫。.NET(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 的憑證存放區(通常是本機電腦的「受信任的根憑證授權單位」)。在程式碼裡停用驗證,會讓應用程式在企業外部網路使用時無法偵測中間人攻擊,漏洞會一直留著。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽