最新消息:20210917 已从crifan.com换到crifan.org

【已解决】HttpWebRequest的GetResponse返回的HttpWebResponse出错:远程服务器返回错误: (401) 未经授权

Web crifan 10087浏览 0评论

【问题】

C#中,提交一个POST的Http请求,准备好了数据,但是最后提交的时候:

HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

返回了错误:

远程服务器返回错误: (401) 未经授权。

【解决过程】

1.严格意义上说,对于现在这个401认证错误的结果,算是可以预见的结果,不完全算是突然出现的错误。

2.只是后来发现,原来在VS2010中,调试过程中,是可以得到更相信的相关的信息的,具体方法是:

点击那个”查看详细信息“,就可以打开对应窗口,看到所有的相关的数据了。包括对应的http的response的内容,此处,看到对应的Header信息:

{X-MSNSERVER: BY2____4011527
BITS-Packet-Type: Ack
X-ClientErrorCode: AccessDenied
Content-Length: 379
Content-Type: application/wls-response-headers+json
Date: Mon, 13 Feb 2012 12:24:32 GMT
P3P: CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"
WWW-Authenticate: WLID1.1 realm="WindowsLive", fault="BadContextToken", policy="MBI_SSL", ver="6.1.6206.0", target="cid-9a8b8bf501a38a36.users.storage.live.com"

}

从中,我们可以看出,对应此401错误的根本原因是:

fault="BadContextToken"

所以,接下来,要做的事情,至少有点方向了,就是去确保,token的正确性。

至于是哪个或哪些cookie的值,则是需要进一步尝试了。

3.期间又经历了漫长的折腾,其中都包括去尝试登陆msn,但是最后提交上传文件的请求后,还是返回上述同样错误:

{X-MSNSERVER: BY2____4011420
BITS-Packet-Type: Ack
X-ClientErrorCode: AccessDenied
Content-Length: 379
Content-Type: application/wls-response-headers+json
Date: Thu, 16 Feb 2012 15:33:50 GMT
P3P: CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"
WWW-Authenticate: WLID1.1 realm="WindowsLive", fault="BadContextToken", policy="MBI_SSL", ver="6.1.6206.0", target="cid-9a8b8bf501a38a36.users.storage.live.com"

}

对于错误中提到的

之前就找到了这个帖子:

SkyDrive – download audio files

其遇到的错误,和我这里的类似,只是所遇到的情况不太相同。

其是下载文件有问题,我是上传文件失败。

该贴中讨论的内容,那位微软官方的人员Federico Raggi提到了,这种错误的原因,可能是由于ticket过期了。

只是对于这方面的内容,没啥概念,也没啥理解。

4 再后来,找到这里:

passportloginservice.cpp

好像就是对应的登陆方面的代码,然后从

BadContextToken –> parseSoapFault –> parseSecureFault –>  最后又回到parseSoapFault 。

但是看到对应的注释:

  // The tickets have expired, require new ones
  else if( faultCode == "q0:BadContextToken" )
  {
    login();

    // Resend the failed message
    SoapMessage *message = getCurrentRequest( true /* copy */ );
    sendSecureRequest( message, inProgressRequests_.value( getCurrentRequest() ) );
  }

其中提到,说明上述的ticket过期了,需要获得一个新的ticket。

这就和上面那位微软的人员所说的,一致了,也就更容易理解了。

意味着,之前验证的信息,已经过期了,需要重新验证。对应的,如果是验证信息错误,那么返回错误就应该是FailedAuthentication,对应的代码是:

  // React accordingly to the received error if related to login issues,
  // if not, fall back to the base class fault parsing
  if( faultCode == "wsse:FailedAuthentication" )
  {
#ifdef KMESSDEBUG_PASSPORTLOGINSERVICE
    kDebug() << "Authentication failed!";
#endif

    emit loginIncorrect();
  }

然后又找到了之前就看到的,关于介绍微软的Live service如何验证方面的官方资料:

Server-to-Server Authentication

所以,剩下的,看起来应该就是,按照官方提示,去重新获得一个ticket,然后再去重新上传文件,也许就可以了。

5.关于登陆window live,有空可以参考这个:

LiveFX + Windows Live ID Client SDK = Safer Program

6.在这里:

https://hg.instantbird.org/instantbird/rev/b07d0d821e80

看到了:

static char *ticket_domains[][2] = { /* http://msnpiki.msnfanatic.com/index.php/MSNP15:SSO */ /* {"Domain", "Policy Ref URI"}, Purpose */ {"messengerclear.live.com", NULL}, /* Authentication for messenger. */ {"messenger.msn.com", "?id=507"}, /* Authentication for receiving OIMs. */ {"contacts.msn.com", "MBI"}, /* Authentication for the Contact server. */ {"messengersecure.live.com", "MBI_SSL"}, /* Authentication for sending OIMs. */ {"spaces.live.com", "MBI"}, /* Authentication for the Windows Live Spaces */ {"livecontacts.live.com", "MBI"}, /* Live Contacts API, a simplified version of the Contacts SOAP service */ {"storage.live.com", "MBI"}, /* Storage REST API */

值得有空去搜索Storage REST API,然后找找相关的API文档和资料看看。

8.一定要好好看看

Live SDK的REST API

里面提到了,用POST方法去上传文件“make a POST request to /FOLDER_ID/files”。

9.这里也有wl(Windows Live)相关的开发文档:

Scopes and permissions

10.这里找到一个更牛的东西,直接有人做好了对应的skydrive的api,支持直接上传文件的:

http://skydriveapiclient.codeplex.com/

下载后,里面包含了对应的uploadFile接口,可以直接供调用。

有空可以试试。

后来去试了skydriveServiceClient:

添加和引用了HgCo.WindowsLive.SkyDriveServiceClient.dll(旧版本是HgCo.WindowsLive.SkyDriveWebClient.dll及其所引用HtmlAgilityPack.dll),然后去测试对应的LogOn函数,结果都失败了。

错误结果如下:

SkyDriveServiceClient.v2.0.2b :超时错误

SkyDriveServiceClient.v2.0.1b :超时错误

SkyDriveServiceClient.v2.0.0b :超时错误

SkyDriveWebClient.0.8.0.a     :未将对象引用设置到对象的实例

SkyDriveWebClient.v0.8.6      :未将对象引用设置到对象的实例

SkyDriveWebClient.v0.8.10     :未将对象引用设置到对象的实例

其中2.0.2b,是当前的最新的版本。

上述结果意味着,对于我这里来说,该库函数,都无法使用。

因此还得我自己去写对应的函数,以实现自己需要的功能。

11.关于MBI和MBI_SSL的测试

后来又去测试了,对于自己的已实现的登陆代码部分。

在刚登陆完成后,就去测试MBI或者MBI_SSL,看看上传文件和创建文件夹部分,是否成功。

具体过程和结果如下:

对于访问

https://login.live.com/ppsecure/post.srf?wa=wsignin1.0&rpsnv=11&ct=1328070682&rver=6.1.6206.0&wp=MBI_SSL_SHARED&wreply=https:%2F%2Fskydrive.live.com%2F&lc=2052&id=250206&mkt=zh-CN&cbcxt=sky&bk=1328070683

提交对应的POST的request之后,获得对应的带有验证信息的cookie后,

再去访问:

https://skydrive.live.com/API/2/AddFolder?lct=1

去创建文件夹,是可以成功创建文件夹的。

但是对于访问类似这样的地址:

https://cid-9a8b8bf501a38a36.users.storage.live.com/users/0x9A8B8BF501A38A36/LiveFolders/InsertSkydriveFile_v2012-01-30-home.7z

去上传文件的话,始终是失败的,错误还是那个BadContextToken+MBI_SSL。

12. 对于前面的访问login.live.com/ppsecure/post.srf给的地址中是wp=MBI_SSL_SHARED

自己试着改成对应的wp=MBI_SSL或者wp=MBI

然后在获得返回的包含认证信息的cookie之后,即可就去上传文件,结果还是失败。

但是无论改成MBI_SSL还是MBI,都还是可以创建文件夹的。

13. 后来结合前面的内容,又多了点理解。

那就是,首先,虽然出现BadContextToken+MBI_SSL的错误,但是此ticket未过期,那是肯定的,

因为如果过期了,那创建文件夹也会失败才对,但是实际是可以的,

加上对应的获得认证的信息前后,都是秒或分钟级别的,所以不存在token或者ticket过期问题。

其次,之所以能够创建文件夹,那是因为根据前面的别人的代码:

{"spaces.live.com", "MBI"}, /* Authentication for the Windows Live Spaces */

{"storage.live.com", "MBI"}, /* Storage REST API */

猜得,前面一直访问的login.live.com和最后创建文件夹的skydrive.live.com,应该都是使用MBI的policy,

而上传文件访问的地址是cid-xxx.users.storage.live.com,此domain要求的应该是MBI_SSL,

所以用之前获得的MBI的policy,去访问users.storage.live.com,是无法成功的,然后人家返回的错误内容告诉你要求的

policy="MBI_SSL"

因此,接下来的事情,就是如何去获得对应的针对users.storage.live.com的MBI_SSL的policy了。

14.刚又注意到,其实上传文件的地址

cid-xxx.users.storage.live.com

是属于{"storage.live.com", "MBI"}中的,即应该就是MBI的policy,而之前访问

login.live.com/ppsecure/post.srf

而获得的policy,好像是MBI_SSL,至少最开始默认的是MBI_SSL_SHARED,所以,上面理解的也许是错了。

实际应该是storage.live.com的是MBI,而别的是MBI_SSL。

15.刚又从

http://www.copypastecode.com/45352/

看到:

spRSTParams[0].wzServiceTarget = messengerclear.live.com

spRSTParams[0].wzServicePolicy = MBI_KEY_OLD

spRSTParams[1].wzServiceTarget = msn.com

spRSTParams[1].wzServicePolicy = LBI

spRSTParams[2].wzServiceTarget = messengersecure.live.com

spRSTParams[2].wzServicePolicy = MBI_SSL

spRSTParams[3].wzServiceTarget = contacts.msn.com

spRSTParams[3].wzServicePolicy = MBI

spRSTParams[4].wzServiceTarget = storage.msn.com

spRSTParams[4].wzServicePolicy = MBI

spRSTParams[5].wzServiceTarget = sup.live.com

spRSTParams[5].wzServicePolicy = MBI

spRSTParams[6].wzServiceTarget = skydrive.live.com

spRSTParams[6].wzServicePolicy = MBI

spRSTParams[7].wzServiceTarget = auth.bay.livefilestore.com

spRSTParams[7].wzServicePolicy = MBI

spRSTParams[8].wzServiceTarget = spaces.live.com

spRSTParams[8].wzServicePolicy = MBI

spRSTParams[9].wzServiceTarget = directory.services.live.com

spRSTParams[9].wzServicePolicy = MBI

spRSTParams[10].wzServiceTarget = mail.live.com

spRSTParams[10].wzServicePolicy = MBI

所以,确定skydrive.live.com是MBI的Policy了,所以,现在是要搞清楚如何获得对应的MBI的policy,

然后再去上传文件。

16.关于如何获得MBI的Policy,通过简单的该最开始传递给login.live.com/ppsecure/post.srf的地址中改为MBI的方法,是不行了。

其他还有何办法,就得再去找资料了。

17.微软关于Live SDK,有个在线交互式的网页供浏览参考代码,很是好用:

http://isdk.dev.live.com/

18.刚根据

http://msdn.microsoft.com/en-us/library/hh243646

去访问我自己的地址:

https://apis.live.net/v5.0/9a8b8bf501a38a36/

可以得到对应返回的内容:

"{

"id": "9a8b8bf501a38a36",

"name": "tian wang",

"first_name": "tian",

"last_name": "wang",

"gender": "male",

"locale": "en_US"

}"

19.

参考这里:

http://msdn.microsoft.com/en-us/windowslive/hh278363

去尝试登陆试试。

20.前后对于LiveConnect的API试了一些,

也看了很多相关资料,但是最后的最后,决定放弃使用这套api,

主要原因在于,

其支持的三种方式使用这套LiveConnect的API,

有REST,C#,Javascript。

对于REST不熟悉,没概念,看了REST简单的介绍后,还是无法短期内搞定。

而对于Javascript,则不适用我这里的C#项目。

对于C#,原以为可以参考微软的代码,以及前面的SkydriveClient的代码,大不了自己一点点实现,

完成对应的认证,然后再使用对应的skydrive的file的操作部分的api,去实现文件的上传。

但是,可恶的是,C#的参考代码,单独是最开始的login的部分,即实现用户名和密码的认证部分,

都不是普通的我所熟悉的http的部分,而是需要什么微软自己的特殊配置文件,而且还涉及silverlight之类的东西,

而且是网页的界面形式的东西,太繁杂,太不适合简单的通过C#代码,通过访问对应的服务器或者提交对应的http请求,而实现对应的认证,获得对应的token认证信息之类的。

而且还要搞懂Oauth认证:

http://msdn.microsoft.com/en-us/library/hh243647.aspx

结果发现也是需要搞懂太多概念,关键还是其用于网页部分的实现,

而不是C#代码方便操作的。

要方便的实现LiveConnect认证就是极其繁杂的事情,然后再去文件操作其实我倒觉得不是什么大问题,毕竟只是调用对应的函数实现功能而已。

总之,想要通过LiveConnect的API,用C#代码实现认证以及后续的skydrive中的文件的操作,对于我来说,无法短期内完成。

所以,暂时放弃此条路。

忘了说了,参考官网代码,去模拟oauth登陆过程,结果发现,还是跳转到:

https://login.live.com/login.srf?wa=wsignin1.0&rpsnv=11&checkda=1&ct=1329578527&rver=6.1.6208.0&wp=MBI_SSL&wreply=https:%2F%2Foauth.live.com%2Fauthorize%3Fclient_id%3D0000000000000001%26scope%3Dwl.skydrive%26response_type%3Dtoken%26auth_redirect%3Dtrue&lc=1033&id=276649&popupui=1

去登陆而已,其过程,和我之前所模拟的,没啥两样,说白了,还是绕了几部,又回到我自己之前已经走过的路了。

实在很没意思。所以确定要放弃这条路了。

21.还是回到原先的模拟网页登陆的复杂过程吧。

因为这条路,虽然足够复杂,但是自己也已经走了95%了,从开始的skydrive的url跳转,

到后来给定用户名和密码,实现可以登陆skydrive,到后来的都可以创建文件夹了。

只是最后的这一关键步骤,上传文件,遇到问题了,但是核心的所需要的包含认证信息的cookie,也都已经完整的都有了。

只是没搞懂如何去拿着这有效的认证信息,实现往

cid-9a8b8bf501a38a36.users.storage.live.com

上面传文件了。

22。突然想到了,之前某处提及了cookie的有效性,即domain的问题:

即domain为.live.com的cookie,对于skydrive.live.com来说,也是无效的。

所以就去尝试了,把对应获得的所有的.live.com的cookie的domain,

都改为我这里的,当前为创建文件而访问的网址的domain:

cid-9a8b8bf501a38a36.users.storage.live.com

结果就可以成功创建文件了。


对应返回的内容是:

{"StatusCode":"201","StatusDescription":"Created","P3P":"CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"","X-MSNSERVER":"BY2____4011206","Set-Cookie":"RPSMaybe=; path=\/; domain=.live.com; expires=Thu, 30-Oct-1980 16:00:00 GMT;","BITS-Packet-Type":"Ack","BITS-Protocol":"{7df0354d-249b-430f-820d-3d2a9bef4931}","BITS-Session-Id":"1mXcwHgrcMSBuBGkm9lpjV8umhz4At5JEGhY-AAOvE9Pk","Accept-Encoding":"Identity"}

对应的IE9抓取的相关信息是:

{"StatusCode":"201","StatusDescription":"Created","P3P":"CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"","X-MSNSERVER":"BY2____4011319","Set-Cookie":"RPSMaybe=; path=\/; domain=.live.com; expires=Thu, 30-Oct-1980 16:00:00 GMT;","BITS-Packet-Type":"Ack","BITS-Protocol":"{7df0354d-249b-430f-820d-3d2a9bef4931}","BITS-Session-Id":"1mYYyP0Q2O60VwPP2Wj_4VAOLSN_EBGiwi2npYWdt6NPY","Accept-Encoding":"Identity"}

后来又去测试了一下,把domain改为.users.storage.live.com,结果也是可以的,返回内容为:

{"StatusCode":"201","StatusDescription":"Created","P3P":"CP=\"BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo\"","X-MSNSERVER":"BY2____4012011","Set-Cookie":"RPSMaybe=; path=\/; domain=.live.com; expires=Thu, 30-Oct-1980 16:00:00 GMT;","BITS-Packet-Type":"Ack","BITS-Protocol":"{7df0354d-249b-430f-820d-3d2a9bef4931}","BITS-Session-Id":"1m6l37JZnJM59obyGKKpUrkMfoo7JMVbd61sdi2BW-K08","Accept-Encoding":"Identity"}

对应的,又去试了试,把domain改为.storage.live.com,结果就是返回对应的401无授权的错误,没有权限上传文件。

这也是和预想的是一致的。

之前是在提交:

https://login.live.com/ppsecure/post.srf?wa=wsignin1.0&rpsnv=11&ct=1328070682&rver=6.1.6206.0&wp=MBI_SSL_SHARED&wreply=https:%2F%2Fskydrive.live.com%2F&lc=2052&id=250206&mkt=zh-CN&cbcxt=sky&bk=1328070683

之前,把wp=MBI_SSL_SHARED改为了wp=MBI的,

此处试了,去掉更改,直接使用wp=MBI_SSL_SHARED,结果也还是可以的成功创建文件的。


【后记】

那现在就去找找,到底是哪里去设置的对应的这些原先domain为.live.com的cookie,变成domain为

.users.storage.live.com

cid-9a8b8bf501a38a36.users.storage.live.com

的。

好像是这里,访问:

https://cid-9a8b8bf501a38a36.users.storage.live.com/clientaccesspolicy.xml

获得的返回内容是:

<?xml version="1.0" encoding="utf-8"?>

<access-policy>

<cross-domain-access>

<policy>

<allow-from http-request-headers="*">

<domain uri="http://*.skydrive.live.com" />

<domain uri="http://*.photos.live.com" />

<domain uri="http://*.pmese.com" />

<domain uri="http://*.vz.kin.com" />

<domain uri="http://*.vf.kin.com" />

<domain uri="http://msc.wlxrs.com"/>

<domain uri="https://*.skydrive.live.com" />

<domain uri="https://*.photos.live.com" />

<domain uri="https://*.pmese.com" />

<domain uri="https://*.vz.kin.com" />

<domain uri="https://*.vf.kin.com" />

<domain uri="https://skydrive.live.com" />

<domain uri="https://skydrive-df.live.com" />

<domain uri="https://secure.wlxrs.com" />

<domain uri="https://mail.live.com"/>

<domain uri="https://*.mail.live.com"/>

<domain uri="https://msc.wlxrs.com"/>

</allow-from>

<grant-to>

<resource path="/" include-subpaths="true" />

</grant-to>

</policy>

<policy>

<allow-from http-request-headers="GET,POST">

<domain uri="http://*.phx.gbl"/>

<domain uri="http://*.dns.microsoft.com"/>

<domain uri="http://*.redmond.corp.microsoft.com"/>

</allow-from>

<grant-to>

<resource path="/counters" include-subpaths="false"/>

<resource path="/@:prev/counters" include-subpaths="false"/>

<resource path="/@:last/counters" include-subpaths="false"/>

</grant-to>

</policy>

</cross-domain-access>

</access-policy>

个人猜测是,对于想要访问

cid-9a8b8bf501a38a36.users.storage.live.com的http的请求来说,

那么其他domain的地址,比如skydrive.live.com来说,都是属于cross-domain-access跨域名访问,那么上述列表中的,都是允许的。

然后对应的cookie,估计在提交之前,只要是上述地址中的cookie,也都被添加过来了。

而其实对于http的请求,cookie提交的时候,其实也并没有添加对应的domain的值,只是提交了对应的

key和value而已。

而对于此处C#代码,提过cookieContainer来添加的cookie,在去getResponse的时候,对应dcookie,肯定是底层代码管理的,

即如果发现不是当前domain的cookie,即如果发现添加进来的cookie的domain不是cid-9a8b8bf501a38a36.users.storage.live.com或.users.storage.live.com,

那么就是加了也是白加,在提交http请求的那一刻,是不会提交给服务器的,所以,之前虽然cookie的name和value都是对了,但是由于domain不对,所以被过滤掉了,所以才无法创建文件的。


【总结】

【关于模拟网页过程的经验和教训】

1.抓取网页分析所看到的结果中,对于cookie,往往会忽视掉被对应的某个javascript脚本所修改。

而这些javascript脚本,一旦操作了对应的cookie,包括创建一个新的cookie,改了已有cookie的值,

使某些cookie失效掉,尤其是改了某些cookie的domain,使得cookie对于原先的domain变的无效了,而对新的domain则有效了。

这类的动作,如果没有注意到,则可能会完全摸不着头脑,搞不懂这些cookie的值的来龙去脉,而无法完全模拟网页的操作。

2.对于模拟访问某些网页,可能要提交很多query string,即url中个那么多&name=value形式的值,和post data中的众多参数和值,

有些是关键的,有些是无所谓的。

对于哪些是关键的,哪些是无所谓的,除了需要自己一点点尝试之外,另外一个需要提醒的是,切莫把太多时间,用于分析每一个值是如何获得的,

而要先去分析一下,哪些是可能重要的,哪些是可能无所谓的,尽量在分析了自己认为重要的之后,就开始去用代码尝试,是否可以获得对应的需要的结果。

这样可以在第一时间,用最短的时间,获得我们所需要的结果,而不用再浪费时间,去计算其他的一些值了。

转载请注明:在路上 » 【已解决】HttpWebRequest的GetResponse返回的HttpWebResponse出错:远程服务器返回错误: (401) 未经授权

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
91 queries in 0.218 seconds, using 23.38MB memory