首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

.NET 獲取瀏覽器完整 Cookie(含 HttpOnly)實戰:P/Invoke 調用 wininet.dll InternetGetCookieEx

     編輯:ASP.NET 基礎

痛點:HttpOnly Cookie 無法被 JavaScript/Document.Cookie 讀取

設定 HttpOnly 旗標的 Cookie,瀏覽器禁止客戶端腳本存取(防 XSS),但伺服器端代碼(如 WebBrowser 控件、爬蟲、自動化測試)常需完整讀取。WebBrowser.Document.Cookie 會忽略 HttpOnly Cookie。解決方案:P/Invoke 調用 Windows IE 核心庫 wininet.dll 的 InternetGetCookieEx。

核心 P/Invoke 定義

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;

internal static class NativeMethods
{
    // 錯誤代碼
    public enum ErrorFlags : uint
    {
        ERROR_INSUFFICIENT_BUFFER = 122,
        ERROR_INVALID_PARAMETER = 87,
        ERROR_NO_MORE_ITEMS = 259
    }

    // 網際網路旗標
    public enum InternetFlags : uint
    {
        INTERNET_COOKIE_HTTPONLY = 0x2000,      // 8192 - 需 IE 8+
        INTERNET_COOKIE_THIRD_PARTY = 0x20000,  // 131072
        INTERNET_FLAG_RESTRICTED_ZONE = 0x10    // 16
    }

    // 關鍵 API:Unicode 版本
    [SuppressUnmanagedCodeSecurity]
    [SecurityCritical]
    [DllImport("wininet.dll", EntryPoint = "InternetGetCookieExW", 
               CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool InternetGetCookieEx(
        [In] string url,
        [In] string cookieName,
        [Out] StringBuilder cookieData,
        [In, Out] ref uint pchCookieData,
        uint flags,
        IntPtr reserved);
}

完整 Cookie 讀取類別


public static class FullWebBrowserCookie
{
    /// 
    /// 取得指定 URI 的所有 Cookie(含 HttpOnly),回傳 Dictionary<Name, Value>
    /// 
    public static Dictionary<string, string> GetCookieList(Uri uri, bool throwIfNoCookie = false)
    {
        var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        string cookieStr = GetCookieInternal(uri, throwIfNoCookie);
        if (string.IsNullOrEmpty(cookieStr)) return dict;

        // 解析 "name1=value1; name2=value2" 格式
        foreach (var pair in cookieStr.Split(';'))
        {
            var kv = pair.Split(new[] { '=' }, 2);
            string key = kv[0].Trim();
            string val = kv.Length > 1 ? kv[1].Trim() : string.Empty;
            if (!dict.ContainsKey(key)) dict[key] = val;
        }
        return dict;
    }

    /// 
    /// 取得單一 Cookie 值
    /// 
    public static string GetCookieValue(string key, Uri uri, bool throwIfNoCookie = false)
    {
        var dict = GetCookieList(uri, throwIfNoCookie);
        return dict.TryGetValue(key, out var val) ? val : null;
    }

    private static string GetCookieInternal(Uri uri, bool throwIfNoCookie)
    {
        uint size = 0;
        string url = UriToString(uri);
        uint flags = (uint)NativeMethods.InternetFlags.INTERNET_COOKIE_HTTPONLY;

        // 第一次呼叫:取得所需緩衝區大小
        if (!NativeMethods.InternetGetCookieEx(url, null, null, ref size, flags, IntPtr.Zero))
        {
            int err = Marshal.GetLastWin32Error();
            if (throwIfNoCookie || err != (int)NativeMethods.ErrorFlags.ERROR_NO_MORE_ITEMS)
                throw new Win32Exception(err);
            return null;
        }

        // 分配緩衝區並第二次呼叫取得資料
        size++; // 空間預留
        var sb = new StringBuilder((int)size);
        if (!NativeMethods.InternetGetCookieEx(url, null, sb, ref size, flags, IntPtr.Zero))
        {
            int err = Marshal.GetLastWin32Error();
            if (throwIfNoCookie || err != (int)NativeMethods.ErrorFlags.ERROR_NO_MORE_ITEMS)
                throw new Win32Exception(err);
            return null;
        }

        // 權限要求
        DemandWebPermission(uri);
        return sb.ToString();
    }

    private static void DemandWebPermission(Uri uri)
    {
        string uriStr = UriToString(uri);
        if (uri.IsFile)
            new System.Security.Permissions.FileIOPermission(
                System.Security.Permissions.FileIOPermissionAccess.Read, uri.LocalPath).Demand();
        else
            new System.Net.WebPermission(System.Net.NetworkAccess.Connect, uriStr).Demand();
    }

    private static string UriToString(Uri uri)
    {
        if (uri == null) throw new ArgumentNullException(nameof(uri));
        UriComponents comp = uri.IsAbsoluteUri ? UriComponents.AbsoluteUri : UriComponents.SerializationInfoString;
        return new StringBuilder(uri.GetComponents(comp, UriFormat.SafeUnescaped), 2083).ToString();
    }
}

使用範例

// WinForms / WPF / Console 應用程式
var uri = new Uri("https://example.com");
try
{
    var cookies = FullWebBrowserCookie.GetCookieList(uri);
    foreach (var kv in cookies)
        Console.WriteLine($"{kv.Key} = {kv.Value}");

    // 取得特定 HttpOnly Cookie
    string authToken = FullWebBrowserCookie.GetCookieValue("AuthToken", uri);
    Console.WriteLine($"AuthToken: {authToken}");
}
catch (Win32Exception ex)
{
    Console.WriteLine($"讀取失敗:{ex.Message} (ErrorCode: {ex.NativeErrorCode})");
}
catch (SecurityException ex)
{
    Console.WriteLine($"權限不足:{ex.Message}");
}

應用場景與注意事項

場景說明
WebBrowser 控件自動化登入登入後調用此類別取得完整 Cookie,供後續 HttpWebRequest/HttpClient 攜帶,實現「免密碼」後續請求。
爬蟲/測試工具模擬瀏覽器行為,需完整 Cookie 容器(含 HttpOnly、Secure、SameSite)。
單點登出同步讀取多個域名下的 Session Cookie,統一清除。

⚠️ 重要限制

  • 僅限 Windows + IE 環境:依賴 wininet.dll(IE 核心)。.NET Core / .NET 5+ 跨平台專案**無法使用**。
  • 需 IE 8 以上:INTERNET_COOKIE_HTTPONLY 旗標 IE 8 才支援。
  • 只能讀取當前使用者 IE Cookie 存儲:無法讀取 Chrome/Firefox/Edge (Chromium) 的 Cookie。
  • 權限問題:調用進程需有網路/檔案存取權限,ClickOnce/Partial Trust 環境可能失敗。

現代替代方案(跨平台、無 IE 依賴)

  1. HttpClient + CookieContainer:程式碼自行管理 Cookie,登入後手動提取 Set-Cookie 標頭存入容器,後續請求自動攜帶。推薦做法。
  2. Puppeteer Sharp / Playwright:Headless Chrome 自動化,可存取完整瀏覽器 Cookie 存儲(含 HttpOnly)。
  3. WebView2 (Edge Chromium):取代 WebBrowser 控件,提供 CoreWebView2.CookieManager 完整管理 Cookie。
  4. Selenium WebDriver:driver.Manage().Cookies.AllCookies 取得所有 Cookie。
  5. 總結

    透過 P/Invoke 調用 wininet.dll!InternetGetCookieExW 並傳遞 INTERNET_COOKIE_HTTPONLY 旗標,是傳統 .NET Framework + WebBrowser 控件環境下讀取 HttpOnly Cookie 的唯一可行方案。代碼需妥善處理緩衝區兩次呼叫、錯誤代碼、權限要求。對於新專案或跨平台需求,**請直接改用 HttpClient 自行管理 Cookie 或採用現代瀏覽器自動化庫**,避免綁定 IE 與 Windows 平台。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved