在寫Internet應(yīng)用程序的時候,常常需要處理用戶登錄的情況。一般來說,對于這種情況,我們是使用程序來模擬用戶在Web頁面上填寫用戶名、密碼并提交的過程。當(dāng)用戶在Web頁面上輸入了用戶名、密碼并提交之后,實(shí)際上是觸發(fā)了一個POST請求,在這個請求中包含有用戶名、密碼等信息。因此,我們只要在程序中將相關(guān)信息封裝成一條POST請求,并將它發(fā)送給Web Server,基本上就能實(shí)現(xiàn)登錄了。以MFC為例,下面的這段代碼模擬了一個登錄過程:
CString strHeaders = _T("Content-Type: application/x-www-form-urlencoded");
// name = "sam", password = "123", action = "submit"
CString strFormData = _T("name=sam&password=123&action=submit");
CInternetSession session;
CHttpConnection* pConnection =
session.GetHttpConnection(_T("ServerNameHere"));
CHttpFile* pFile =
pConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST,
_T("FormActionHere"));
BOOL result = pFile->SendRequest(strHeaders,
(LPVOID)(LPCTSTR)strFormData, strFormData.GetLength());
這個方法對于Asp頁面很有效,但對于Asp.Net頁面,有時卻不起作用,這是為什么呢?
為了搞清出Asp.Net頁面在處理登錄時與Asp頁面有何區(qū)別,我們需要使用Sniffer工具來跟蹤Web服務(wù)器與瀏覽器之間的通訊。經(jīng)過跟蹤會發(fā)現(xiàn),Asp.Net頁面在用戶提交登錄信息之后,仍然是使用POST請求將相關(guān)信息發(fā)送給服務(wù)器。所不同的是,處理用戶名、密碼等信息之外還多了一個__VIEWSTATE。如果在上面代碼中的strFormData中加上一個通過Sniffer得到的__VIEWSTATE的話,就能夠成功模擬出整個登錄過程了。接下來的問題就是,我們應(yīng)該如何獲得這個__VIEWSTATE呢?
我們知道,Asp.Net頁面有一個ViewState屬性,Asp.Net用它來保存頁面的狀態(tài)信息,以便在頁面提交失敗時,能夠恢復(fù)頁面的狀態(tài)。它是通過頁面中的一個隱藏的域來定義的,如果通過瀏覽器來View Source的話,可以看到它是如下的一行代碼:
它的value值正是我們所需要的,我們只要從登錄頁面中解析出這個__VIEWSTATE的value,我們的問題就能夠得到解決了。
仔細(xì)看一下,ViewState的值是經(jīng)過編碼的,先不管它,直接將它從頁面中取出,和登錄信息一起組成POST請求,發(fā)送給Server,結(jié)果如何呢?失敗了L。對比一下Sniffer的結(jié)果和頁面中ViewState的value,我們會發(fā)現(xiàn),它們之間還是有些許不同的。原來,頁面源碼中的ViewState值是經(jīng)過Base64編碼的,而當(dāng)它被發(fā)送給Web Server時,為了保證傳輸?shù)恼_,瀏覽器會將它轉(zhuǎn)換成URL編碼,當(dāng)Web Server接收到ViewState之后,當(dāng)然會先將它從URL編碼解碼為Base64編碼再交給Asp.Net處理??磥砦覀冃枰獙iewState的值在進(jìn)行一邊URL編碼處理,這樣就能夠成功模擬整個登錄過程了J。
參考
1. HOWTO: Simulate a Form POST Request Using WinInet,微軟的KB文章,描述了模擬POST請求的實(shí)現(xiàn)。
2. ASP .NET Maintaining the ViewState,ViewState的入門知識。
3. ViewState: All You Wanted to Know,關(guān)于ViewState的深入討論。
4. ViewState Parser,想看看解碼后的ViewState是什么樣子嗎?試試這個Parser。
5. 博客堂中的相關(guān)討論,這是我在解決這個問題的過程中,在博客堂寫的Blog。