【问题标题】:C# WebClient HTTP Basic Authentication Failing 401 with Correct CredentialsC# WebClient HTTP 基本身份验证失败 401 正确凭证
【发布时间】:2015-02-25 02:16:53
【问题描述】:

我正在尝试通过 自动配置无线路由器的 SSID 和密码。路由器没有我知道的 API。这是一个无品牌的中国路由器。网络配置似乎是唯一的配置选项。它使用(您浏览到路由器的 IP 地址并获得一个询问用户名和密码的通用对话框)。

当我手动更新 SSID 和密码(两个单独的表单)时,我使用 Wireshark 来获取 请求使用的标题和表单字段。然后我尝试使用 来模拟那些 请求。

这是我用来尝试保存新 SSID 的一段代码(NameValueCollection 在别处定义):

private const string FORM_SSID = "http://192.168.1.2/formWlanSetup.htm";
private const string REF_SSID = "http://192.168.1.2/formRedirect.htm?redirect-url=wlbasic.htm&wlan_id=0";
private NameValueCollection mFields = HttpUtility.ParseQueryString(string.Empty, Encoding.ASCII);

public string SaveConfigResponse()
{
    try
    {
        using (WebClient wc = new WebClient())
        {
            wc.Headers[HttpRequestHeader.Accept] = "text/html, application/xhtml+xml, */*";
            wc.Headers[HttpRequestHeader.Referer] = REF_SSID;
            wc.Headers[HttpRequestHeader.AcceptLanguage] = "en-US";
            wc.Headers[HttpRequestHeader.UserAgent] = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko";
            wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
            wc.Headers[HttpRequestHeader.AcceptEncoding] = "gzip, deflate";
            wc.Headers[HttpRequestHeader.Host] = "192.168.1.2";
            wc.Headers[HttpRequestHeader.Connection] = "Keep-Alive";
            wc.Headers[HttpRequestHeader.ContentLength] = Encoding.ASCII.GetBytes(mFields.ToString()).Length.ToString();
            wc.Headers[HttpRequestHeader.CacheControl] = "no-cache";
            string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(config_user + ":" + config_pass));
            wc.Headers[HttpRequestHeader.Authorization] = string.Format("Basic {0}", credentials);
            //wc.Credentials = new NetworkCredential("admin", "admin");
            return Encoding.ASCII.GetString(wc.UploadValues(FORM_SSID, "POST", mFields));
        }
    }
    catch (Exception ex)
    {
        return ex.Message;
    }
}

这会导致 未授权响应。我正在尝试做的事情是不可能的吗?


更新

这里是浏览器 post/response 和 WebClient post/response 的 HTTP 标头。再次,我尝试将我看到的浏览器发布的内容与我的 WebClient 发布内容进行匹配。

浏览器:

POST /formWlanSetup.htm HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://192.168.1.2/formRedirect.htm?redirect-url=wlbasic.htm&wlan_id=0
Accept-Language: en-US
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: 192.168.1.2
Content-Length: 524
Connection: Keep-Alive
Cache-Control: no-cache
Authorization: Basic YWRtaW46YWRtaW4=

HTTP/1.1 302 Found
Location: wlbasic.htm
Content-Length: 183
Date: Thu, 23 Oct 2014 18:18:27 GMT
Server: eCos Embedded Web Server
Connection: close
Content-Type: text/html
Transfer-Encoding: chunked
Cache-Control: no-cache

网络客户端:

POST /formWlanSetup.htm HTTP/1.1
Accept-Language: en-US
Accept-Encoding: gzip, deflate
Cache-Control: no-cache
Authorization: Basic YWRtaW46YWRtaW4=
Accept: text/html, application/xhtml+xml, */*
Content-Type: application/x-www-form-urlencoded
Referer: http://192.168.1.2/formRedirect.htm?redirect-url=wlbasic.htm&wlan_id=0
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Host: 192.168.1.2
Content-Length: 524
Connection: Keep-Alive

HTTP/1.1 401 Not Authorized
WWW-Authenticate: Basic realm="AP"
Date: Thu, 23 Oct 2014 18:18:41 GMT
Server: eCos Embedded Web Server
Connection: close
Content-Type: text/html
Transfer-Encoding: chunked
Cache-Control: no-cache

再一次,这一切都是从 Wireshark 收集到的。我对 Wireshark 不是很熟悉,但我能够做到这一点。如果我知道如何正确提取原始数据包数据并粘贴它,我会的。

重要的新观察

  • Wireshark 从 Browser 和 WebClient 捕获的 post 数据包在标头的顺序上明显不同。不过,我不知道这可能有多大意义,也可能没有,因为每个标题的数据显然是相同的。
  • 我注意到的数据包之间的一个明显区别是 Wireshark 报告的 Browser 数据包明显大于 WebClient 数据包。查看逐项视图,我找不到任何明显的差异。我认为发布原始数据进行比较会发现很多信息,但同样,我真的不知道该怎么做。
  • 我有一个令人困惑的启示。 尽管回复中明确指出“(401) Unauthorized”,但该帖子实际上已被路由器接受!在我的 WebClient 帖子显示设置已被接受并保存后,进入路由器的网络配置。

最后一个是个大人物。我发现自己处于可以通过 WebClient 帖子保存配置的情况,但我必须忽略 401 响应才能这样做。显然,这远非理想。如此接近,却又如此遥远!


最终更新(解决方案)

我已经解决了基本身份验证失败的问题,尽管WebClient 没有。我使用了@caesay 的建议并选择了HttpWebRequest(连同WebResponse)。我的表单帖子会导致重定向,所以我必须允许这样做。

这基本上是我的选择:

private bool ConfigureRouter()
{
    bool passed = false;
    string response = "";
    HttpWebRequest WEBREQ = null;
    WebResponse WEBRESP = null;            

    // Attempt to POST form to router that saves a new SSID.
    try
    {
        var uri = new Uri(FORM_SSID); // Create URI from URL string.
        WEBREQ = HttpWebRequest.Create(uri) as HttpWebRequest;

        // If POST will result in redirects, you won't see an "OK"
        // response if you don't allow those redirects
        WEBREQ.AllowAutoRedirect = true;

        // Basic authentication will first send the request without 
        // creds.  This is protocol standard.
        // When the server replies with 401, the HttpWebRequest will
        // automatically send the request again with the creds when
        // when PreAuthenticate is set.
        WEBREQ.PreAuthenticate = true;
        WEBREQ.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested;

        // Mimic all headers known to satisfy the request
        // as discovered with a tool like Wireshark or Fiddler
        // when the form was submitted from a browser.
        WEBREQ.Method = "POST";
        WEBREQ.Accept = "text/html, application/xhtml+xml, */*";
        WEBREQ.Headers.Add("Accept-Language", "en-US"); // No AcceptLanguage property built-in to HttpWebRequest
        WEBREQ.UserAgent = USER_AGENT;
        WEBREQ.Referer = REF_SSID;
        WEBREQ.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
        WEBREQ.KeepAlive = true;
        WEBREQ.Headers.Add("Pragma", "no-cache"); // No Pragma property built-in to HttpWebRequest

        // Use a cached credential so that the creds are properly
        // submitted with subsequent redirect requests.
        CredentialCache creds = new CredentialCache();
        creds.Add(uri, "Basic", new NetworkCredential(config_user, config_pass));
        WEBREQ.Credentials = creds;

        // Submit the form.
        using (Stream stream = WEBREQ.GetRequestStream())
        {
            SSID ssid = new SSID(ssid_scanned); // Gets predefined form fields with new SSID inserted (NameValueCollection PostData)
            stream.Write(ssid.PostData, 0, ssid.PostData.Length);
        }

        // Get the response from the final redirect.
        WEBRESP = WEBREQ.GetResponse();
        response = ((HttpWebResponse)WEBRESP).StatusCode.ToString();
        if (response == "OK")
        {
            StatusUpdate("STATUS: SSID save was successful.");
            passed = true;
        }
        else
        {
            StatusUpdate("FAILED: SSID save was unsuccessful.");
            passed = false;
        }
        WEBRESP.Close();
    }
    catch (Exception ex)
    {
        StatusUpdate("ERROR: " + ex.Message);
        return false;
    }
    return passed;
}

【问题讨论】:

    标签: c# webclient http-basic-authentication http-post webclient post http-status-code-401 c# http-post webclient basic-authentication http-status-code-401


    【解决方案1】:

    我正在尝试做的事情是不可能的吗?

    不,这并非不可能。这些年来,我一直为这样的网络抓取感到头疼,因为有些网络服务器很挑剔,而且你的路由器接口可能是一个自定义的网络服务器实现,不像 apache 或 iis 那样宽容。

    我会进行一次 Wireshark 捕获并获取 chrome 发送的原始数据包数据(w/payload 等),然后为您的应用程序进行相同的捕获。确保数据包尽可能相似。如果您仍然有问题,请将数据包捕获发布到 pastebin 或其他东西,以便我们查看。

    编辑::

    不要使用受限的WebClient API,尝试使用一些较低级别的项目,不知道下面的代码是否适合你:

    var uri = new Uri("http://192.168.1.2/formWlanSetup.htm");
    var cookies = new CookieContainer();
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.CookieContainer = cookies;
    request.ServicePoint.Expect100Continue = false;
    request.Method = "POST";
    request.ContentType = "application/x-www-form-urlencoded";
    request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko";
    request.Referer = "http://192.168.1.2/formRedirect.htm?redirect-url=wlbasic.htm&wlan_id=0";
    request.Credentials = new NetworkCredential(config_user, config_pass);
    request.PreAuthenticate = true;
    var response = request.GetResponse();
    var reader = new StreamReader(response.GetResponseStream());
    string htmlResponse = reader.ReadToEnd();
    

    【讨论】:

    • 嗯。我在上面添加了您的代码并将 uri 传递给 UploadValues 而不是字符串。我现在得到一个 WebClient 异常:“WebClient 请求期间发生异常。”内部异常:“必须使用适当的属性或方法修改 'Content-Length' 标头。\r\n参数名称:名称”。我的 ContentLength 标头现在不知何故错了?
    • @Solipcyst;哦,您不应该自己设置 contentlength 标头...框架会自动将其设置为正确的值。
    • 感谢您迄今为止的帮助。我用一些有趣的新发现更新了我的帖子。如果您对我如何从 Wireshark 中提取人类可读的“原始”数据包数据有任何快速的见解,我会全力以赴。这种事情超出了我的舒适区。我现在能做的最好的就是比较 HTTP 标头和表单数据。
    • @Solipcyst:嘿,我有预感怎么了,你为什么用UploadValues的方法上传一个空的NameValueCollection?毕竟,如果没有要发布的值,浏览器不会将内容类型设置为application/x-www-form-urlencoded
    • 我可能对NameValueCollection 没有我想的那么清楚。列表不为空。它已在此方法之前的其他地方定义。
    猜你喜欢
    • 2013-09-22
    • 1970-01-01
    • 2016-05-10
    • 2011-08-17
    • 1970-01-01
    • 2016-01-10
    • 2022-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多