2011-09-01 42 views
16

This question has been asked here before。接受的答案對提問者和回答者都可能是顯而易見的 - 但對我來說不是這樣。我已經評論了上述問題以獲得更多精確度,但沒有迴應。 I also approached the meta Q&A尋求幫助,如何從他們的墳墓帶回問題,也沒有答案。如何從使用OpenID的網站請求頁面?

的答案在這裏上面的問題是:

從客戶端的角度來看,OpenID登錄非常相似,任何其他基於網絡的登錄。客戶端沒有定義的協議;這是一個普通的Web會話,根據您的OpenID提供程序而有所不同。出於這個原因,我懷疑是否存在這樣的圖書館。您可能需要自己編寫代碼。

我知道如何log onto a website with Python已經使用Urllib2模塊。但是,這還不足以讓我猜測如何對OpenID進行身份驗證。

實際上,我試圖讓my StackOverflow inbox in json format,爲此,我需要先登錄。

有人能提供很短的介紹或者對如何做到這一點很好的教程鏈接?

+0

PS:我已經將這個帖子標記爲主持人關於故意複製的注意。 – Benjamin

回答

3

這個答案總結別人怎麼說以下,尤其是RedBaron,再加上加我用使用谷歌帳戶才能到StackOverflow的收件箱的方法。

使用Firefox的篡改數據的開發工具,並登錄到計算器,人們可以看到,OpenID的工作是這樣的:

  1. 的StackOverflow從給定服務(這裏谷歌),在發佈定義請求驗證數據;
  2. Google帳戶接管並檢查已存在的Cookie作爲身份驗證的證據;
  3. 如果找不到cookie,Google會請求驗證並設置cookie;
  4. 一旦cookie被設置,StackOverflow確認用戶的身份驗證。

以上總結了這個過程,實際上這個過程比較複雜,因爲許多重定向和cookie交換確實發生了。

由於通過程序再現相同的過程證明了某種程度上的困難(這可能只是我的文盲),特別是試圖追捕URL以調用所有語言環境細節等。我選擇先登錄Google帳戶,當之無愧的cookie,然後登錄到Stackoverflow,它將使用Cookie進行身份驗證。

這是通過使用以下Python模塊完成的:urllib,urllib2,cookielib和BeautifulSoup。

這是(簡化)代碼,它不是完美的,但它的確有用。擴展版本可在Github上找到。

#!/usr/bin/env python 

import urllib 
import urllib2 
import cookielib 
from BeautifulSoup import BeautifulSoup 
from getpass import getpass 

# Define URLs 
google_accounts_url = 'http://accounts.google.com' 
authentication_url = 'https://accounts.google.com/ServiceLoginAuth' 
stack_overflow_url = 'https://stackoverflow.com/users/authenticate' 
genuwine_url = 'https://stackoverflow.com/inbox/genuwine' 

# Build opener 
jar = cookielib.CookieJar() 
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(jar)) 

def request_url(request):  
    ''' 
     Requests given URL. 
    '''  
    try: 
     response = opener.open(request) 
    except: 
     raise 
    return response 


def authenticate(username='', password=''):   
    ''' 
     Authenticates to Google Accounts using user-provided username and password, 
     then authenticates to StackOverflow. 
    ''' 
    # Build up headers 
    user_agent = 'Mozilla/5.0 (Ubuntu; X11; Linux i686; rv:8.0) Gecko/20100101 Firefox/8.0' 
    headers = {'User-Agent' : user_agent} 

    # Set Data to None 
    data = None 

    # Build up URL request with headers and data  
    request = urllib2.Request(google_accounts_url, data, headers) 
    response = request_url(request) 

    # Build up POST data for authentication 
    html = response.read() 
    dsh = BeautifulSoup(html).findAll(attrs={'name' : 'dsh'})[0].get('value').encode() 

    auto = response.headers.getheader('X-Auto-Login') 

    follow_up = urllib.unquote(urllib.unquote(auto)).split('continue=')[-1] 

    galx = jar._cookies['accounts.google.com']['/']['GALX'].value 

    values = {'continue' : follow_up, 
       'followup' : follow_up, 
       'dsh' : dsh, 
       'GALX' : galx, 
       'pstMsg' : 1, 
       'dnConn' : 'https://accounts.youtube.com', 
       'checkConnection' : '', 
       'checkedDomains' : '', 
       'timeStmp' : '', 
       'secTok' : '', 
       'Email' : username, 
       'Passwd' : password, 
       'signIn' : 'Sign in', 
       'PersistentCookie' : 'yes', 
       'rmShown' : 1} 

    data = urllib.urlencode(values) 

    # Build up URL for authentication 
    request = urllib2.Request(authentication_url, data, headers) 
    response = request_url(request) 

    # Check if logged in 
    if response.url != request._Request__original: 
     print '\n Logged in :)\n' 
    else: 
     print '\n Log in failed :(\n' 

    # Build OpenID Data  
    values = {'oauth_version' : '', 
       'oauth_server' : '', 
       'openid_username' : '', 
       'openid_identifier' : 'https://www.google.com/accounts/o8/id'} 

    data = urllib.urlencode(values) 

    # Build up URL for OpenID authetication 
    request = urllib2.Request(stack_overflow_url, data, headers) 
    response = request_url(request) 

    # Retrieve Genuwine 
    data = None 
    request = urllib2.Request(genuwine_url, data, headers) 
    response = request_url(request) 
    print response.read() 


if __name__ == '__main__': 
    username = raw_input('Enter your Gmail address: ') 
    password = getpass('Enter your password: ') 
    authenticate(username, password) 
+0

呃,爲什麼HTMLParser而不是像BeautifulSoup? – ThiefMaster

+0

@ThiefMaster:如果您認爲BeautifulSoup爲此目的比HTMLParser更好,那麼請隨時解釋並修改答案。 – Benjamin

+0

兩個簡單的函數調用與子類化和6個縮進級別 – ThiefMaster

0

您需要在任何「登錄」頁面上使用cookie,在Python中使用cookiejar。例如:

jar = cookielib.CookieJar() 
myopener = urllib2.build_opener(urllib2.HTTPCookieProcessor(jar)) 
#myopener now supports cookies. 
.... 
11

那麼我自己對OpenID不太瞭解,但是你的帖子(和賞金!!)讓我感興趣。

This link講述了OpenID認證序列的確切流程(Atleast for v1.0,新版本爲2.0)。從我可以做出來的步驟會是這樣的

  1. 您提取的stackoverflow登錄頁面,也將提供一個選項使用OpenID(作爲一個表單域)登錄。
  2. 您發送烏爾的OpenID這實際上是URI的一種形式,不是用戶名/電子郵件(如果它是谷歌個人資料是你的個人資料ID),然後
  3. #1將連接到您的ID供應商(在這種情況下,谷歌)和發送您重定向到Google登錄頁面,另一個鏈接到您稍後應該重定向(可以說一個
  4. 您可以登錄到谷歌的傳統(使用Python的POST方法)
  5. 谷歌提供了一個密碼令牌提供頁面(不太確定這一步)作爲對您登錄請求的回覆
  6. 您發送新的請求st到a與此令牌。
  7. Stackoverflow將通過此令牌聯繫Google。如果建立了真實性,它將返回一個會話ID
  8. 以後對STackOverflow的請求應該有這個會話ID
  9. 不知道退出!

This link講述了OpenID中的各種響應及其含義。所以,當你的代碼代表你的客戶時,它可能會派上用場。從維基頁面

鏈接OpenID Explained

編輯:使用篡改數據添加對Firefox,可以構造以下的事件序列。

  1. 用戶發送一個請求到SO登錄頁面。在表單字段中輸入openID後,結果頁面會將302重定向到一個谷歌頁面。 重定向網址有很多OpenID參數(這是用於谷歌服務器)。其中之一是return_to = https://stackoverflow.com/users/authenticate/?s=some_value
  2. 用戶會看到google登錄頁面。在登錄時,有幾個302在google領域將用戶重定向。
  3. 最後一個302接收到重定向用戶到計算器在「的return_to」早期
  4. 指定的頁面。在這整個系列已經產生了大量的cookie的運作必須正確
  5. 存儲在訪問SO頁面(這是谷歌302'd),SO服務器處理您的請求,並在響應頭中發送一個字段「Set-Cookie」,將名爲gauth和usr的cookie與另一個302一起設置爲stackoverflow.com。此步驟完成您的登錄
  6. 您的客戶只需存儲cookie usr
  7. 只要您記住發送cookie到usr,併發送任何請求到SO,您就已經登錄。
  8. 您現在可以請求您的收件箱記住發送帶請求的usr cookie。

我建議你開始編碼你的python客戶端並仔細研究響應。在大多數情況下,這將是一系列302用戶最小的用戶干預(除了填寫您的Google用戶名和密碼,並允許網站頁面)。

但是爲了使它更容易,您可以使用瀏覽器登錄到SO,複製所有cookie值並使用設置了cookie值的urllib2發出請求。

當然,如果您註銷瀏覽器,您將不得不再次登錄並更改python程序中的cookie值。

+0

好的,所以我登錄到StackOverflow,它將我重定向到例如的登錄頁面。谷歌,一旦登錄,將我重新導向到SO。 但是您請求哪個URL然後登錄到SO? 通常,您請求一個URL,它會向您發送一個301錯誤,然後您將獲得身份驗證標頭,然後將該方案和領域與用戶名和密碼一起添加到請求標頭。 現在,如果我請求http://stackoverflow.com或http://stackoverflow.com/users/login,我沒有得到任何301錯誤的第一個地方。 – Benjamin

+0

我認爲,一旦你登錄到你的谷歌帳戶,它應該發送一個重定向回SO(成功驗證並允許網站頁面)。重定向網址應該包含一個參數,其中包含Google爲SO生成的加密標記。一旦您重定向到SO頁面,SO服務器將處理該令牌,如果發現正確,則爲您的其餘會話設置一個cookie。然後你登錄到SO。只要您向該請求發送Cookie,您就可以向SO請求任何頁面,並且SO會將該頁面發回給您。 – RedBaron

+0

使用Firefox我檢測到我登錄到Google帳戶後訪問了以下頁面。 http://stackauth.com/auth/global/write?authToken=SomeValue。 現在,無論是谷歌重定向到這個網站還是在早先的通訊中,都告訴瀏覽器在auth完成時重定向到這個鏈接,我不知道。 你可能應該解析每個請求的響應並分析它,這樣你就可以找到缺失的鏈接(缺少301響應) – RedBaron

0

我做了一個簡單的腳本,使用Mozilla Firefox cookies登錄到stackoverflow.com。它不是完全自動化的,因爲你需要手動登錄,但這是我設法做的。

Scipt實際上是最新版本的FF(我使用8.0.1),但你需要得到最新的sqlite dll,因爲Python 2.7自帶的默認版本無法打開數據庫。你可以在這裏得到它:http://www.sqlite.org/sqlite-dll-win32-x86-3070900.zip

import urllib2 
import webbrowser 
import cookielib 
import os 
import sqlite3 
import re 
from time import sleep 

#login in Firefox. Must be default browser. In other cases log in manually 
webbrowser.open_new('http://stackoverflow.com/users/login') 

#wait for user to log in 
sleep(60) 

#Process profiles.ini to get path to cookies.sqlite 
profile = open(os.path.join(os.environ['APPDATA'],'Mozilla','Firefox','profiles.ini'), 'r').read() 

COOKIE_DB = os.path.join(os.environ['APPDATA'],'Mozilla','Firefox','Profiles',re.findall('Profiles/(.*)\n',profile)[0],'cookies.sqlite') 
CONTENTS = "host, path, isSecure, expiry, name, value" 

#extract cookies for specific host 
def get_cookies(host): 
    cj = cookielib.LWPCookieJar() 
    con = sqlite3.connect(COOKIE_DB) 
    cur = con.cursor() 
    sql = "SELECT {c} FROM moz_cookies WHERE host LIKE '%{h}%'".format(c=CONTENTS, h=host) 
    cur.execute(sql) 
    for item in cur.fetchall(): 
     c = cookielib.Cookie(0, item[4], item[5], 
      None, False, 
      item[0], item[0].startswith('.'), item[0].startswith('.'), 
      item[1], False, 
      item[2], 
      item[3], item[3]=="", 
      None, None, {}) 
     cj.set_cookie(c) 
    return cj 

host = 'stackoverflow' 

cj = get_cookies(host) 

opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) 

response = opener.open('http://stackoverflow.com').read() 

# if username in response - Auth successful 
if 'Stanislav Golovanov' in response: 
    print 'Auth successful' 
+1

您可能已經意識到了此代碼中的缺陷,但我提到了它們以便大家都知道。 1.不可移植(僅適用於Windows)。 2.如果有超過1個Firefox配置文件,則不起作用3.如果用戶在60秒內未登錄,則不起作用4.即使登錄速度超過60秒,也要等待5.無用的服務器端 – rds

5

我知道這是接近考古,挖掘後那兩歲,但我只是寫在驗證答案代碼的一個新的加強版,所以我認爲這可能是冷靜地在這裏分享,因爲這個問題/答案對我來說是非常有幫助的。

所以,這裏有什麼不同:

  • 它採用了新的requests庫,在urllib2增強;
  • 它支持使用谷歌和stackexchange的openid提供商進行身份驗證。
  • 它是方式更短,更易於閱讀,但它具有較小的打印輸出

下面的代碼:

#!/usr/bin/env python 

import sys 
import urllib 
import requests 
from BeautifulSoup import BeautifulSoup 

def get_google_auth_session(username, password): 
    session = requests.Session() 
    google_accounts_url = 'http://accounts.google.com' 
    authentication_url = 'https://accounts.google.com/ServiceLoginAuth' 
    stack_overflow_url = 'http://stackoverflow.com/users/authenticate' 

    r = session.get(google_accounts_url) 
    dsh = BeautifulSoup(r.text).findAll(attrs={'name' : 'dsh'})[0].get('value').encode() 
    auto = r.headers['X-Auto-Login'] 
    follow_up = urllib.unquote(urllib.unquote(auto)).split('continue=')[-1] 
    galx = r.cookies['GALX'] 

    payload = {'continue' : follow_up, 
       'followup' : follow_up, 
       'dsh' : dsh, 
       'GALX' : galx, 
       'pstMsg' : 1, 
       'dnConn' : 'https://accounts.youtube.com', 
       'checkConnection' : '', 
       'checkedDomains' : '', 
       'timeStmp' : '', 
       'secTok' : '', 
       'Email' : username, 
       'Passwd' : password, 
       'signIn' : 'Sign in', 
       'PersistentCookie' : 'yes', 
       'rmShown' : 1} 

    r = session.post(authentication_url, data=payload) 

    if r.url != authentication_url: # XXX 
     print "Logged in" 
    else: 
     print "login failed" 
     sys.exit(1) 

    payload = {'oauth_version' : '', 
       'oauth_server' : '', 
       'openid_username' : '', 
       'openid_identifier' : ''} 
    r = session.post(stack_overflow_url, data=payload) 
    return session 

def get_so_auth_session(email, password): 
    session = requests.Session() 
    r = session.get('http://stackoverflow.com/users/login') 
    fkey = BeautifulSoup(r.text).findAll(attrs={'name' : 'fkey'})[0]['value'] 

    payload = {'openid_identifier': 'https://openid.stackexchange.com', 
       'openid_username': '', 
       'oauth_version': '', 
       'oauth_server': '', 
       'fkey': fkey, 
       } 
    r = session.post('http://stackoverflow.com/users/authenticate', allow_redirects=True, data=payload) 
    fkey = BeautifulSoup(r.text).findAll(attrs={'name' : 'fkey'})[0]['value'] 
    session_name = BeautifulSoup(r.text).findAll(attrs={'name' : 'session'})[0]['value'] 

    payload = {'email': email, 
       'password': password, 
       'fkey': fkey, 
       'session': session_name} 

    r = session.post('https://openid.stackexchange.com/account/login/submit', data=payload) 
    # check if url changed for error detection 
    error = BeautifulSoup(r.text).findAll(attrs={'class' : 'error'}) 
    if len(error) != 0: 
     print "ERROR:", error[0].text 
     sys.exit(1) 
    return session 

if __name__ == "__main__": 
    prov = raw_input('Choose your openid provider [1 for StackOverflow, 2 for Google]: ') 
    name = raw_input('Enter your OpenID address: ') 
    pswd = getpass('Enter your password: ') 
    if '1' in prov: 
     so = get_so_auth_session(name, pswd) 
    elif '2' in prov: 
     so = get_google_auth_session(name, pswd) 
    else: 
     print "Error no openid provider given" 

    r = so.get('http://stackoverflow.com/inbox/genuwine') 
    print r.json() 

代碼也可作爲github gist

HTH

+0

我發佈了一個項目,使用它來使用命令行工具連接到stackoverflow(和聊天):http://github.com/guyzmo/pystackoverflow – zmo

相關問題