2011-09-01 13 views
16

This question has been asked here before. La risposta accettata era probabilmente ovvia sia per l'interlocutore che per la risposta, ma non per me. Ho commentato la domanda sopra per ottenere più precisioni, ma non c'è stata risposta. I also approached the meta Q&A per aiuto su come riportare domande dalla loro tomba, e non ho avuto risposta neanche.Come richiedere le pagine dal sito Web che utilizza OpenID?

La risposta alla domanda di cui sopra è stato qui:

Dal punto di vista del cliente, un login OpenID è molto simile a qualsiasi altro login basato sul web. Non esiste un protocollo definito per il client; è una normale sessione Web che varia in base al provider OpenID. Per questo motivo, dubito che esistano tali librerie. Probabilmente dovrai codificarlo da solo.

So già come log onto a website with Python, utilizzando il modulo Urllib2. Ma non è abbastanza per me indovinare come autenticarsi in un OpenID.

In realtà sto cercando di ottenere my StackOverflow inbox in json format, per cui ho bisogno di essere loggato.

Qualcuno potrebbe fornire una breve introduzione o un link ad un bel tutorial su come farlo?

+0

PS: Ho segnalato questo post già per l'attenzione del moderatore in merito alla duplicazione di proposito. – Benjamin

risposta

3

Questa risposta riassume ciò che altri hanno detto qui di seguito, in particolare RedBaron, più l'aggiunta di un metodo che ho usato per arrivare al StackOverflow Posta in arrivo utilizzando Google Accounts.

Utilizzando lo strumento di sviluppo Tamper Data di Firefox e accesso a StackOverflow, si può vedere che OpenID funziona in questo modo:

  1. StackOverflow richiede l'autenticazione da un determinato servizio (qui di Google), definito nel postato dati;
  2. Google Account prende il sopravvento e verifica un cookie già esistente come prova di autenticazione;
  3. Se non viene trovato alcun cookie, Google richiede l'autenticazione e imposta un cookie;
  4. Una volta impostato il cookie, StackOverflow riconosce l'autenticazione dell'utente.

Quanto sopra riassume il processo, che in realtà è più complicato, poiché molti reindirizzamenti e scambi di cookie si verificano effettivamente.

Perché la riproduzione dello stesso processo si è dimostrata programmaticamente in qualche modo difficile (e potrebbe essere solo il mio analfabetismo), soprattutto cercando di rintracciare gli URL per chiamare tutte le specifiche locali ecc. Ho optato per l'accesso a Google Account prima, ottenendo un cookie ben meritato e quindi accedere a StackOverflow, che utilizzerebbe il cookie per l'autenticazione.

Questo viene fatto semplicemente utilizzando i seguenti moduli Python: urllib, urllib2, cookielib e BeautifulSoup.

Ecco il codice (semplificato), non è perfetto, ma fa il trucco. La versione estesa può essere trovata su 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

Ugh, perché HTMLParser e non qualcosa come BeautifulSoup? – ThiefMaster

+0

@ThiefMaster: se pensi che ci sia una buona ragione per cui BeautifulSoup è meglio di HTMLParser per questo scopo, sentiti libero di spiegare e modificare la risposta. – Benjamin

+0

Due semplici chiamate di funzione in sottoclasse e 6 livelli di indentazione – ThiefMaster

0

È necessario implementare i cookie su qualsiasi pagina di "accesso", in Python si utilizza cookiejar. Per esempio:

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

Beh io stesso non so molto di OpenID, ma il tuo post (e la bontà !!) mi ha interessato.

This link indica il flusso esatto della sequenza di autenticazione OpenID (Atleast per v1.0. La nuova versione è 2.0). Da quello che ho potuto capire, la procedura sarebbe qualcosa di simile

  1. È recuperare la pagina di login di StackOverflow che fornirà anche la possibilità di effettuare il login usando OpenID (come un campo di modulo).
  2. si invia ur OpenID che è in realtà una forma di uri e NON Username/email (se è il profilo di Google è il vostro profilo ID)
  3. StackOverflow si connette al proprio fornitore di ID (in questo caso Google) e inviare un reindirizzamento a Google pagina di login e un altro link dove si dovrebbe reindirizzare tardi (diciamo un)
  4. È possibile accedere a Google fornito pagina convenzionalmente (utilizzando il metodo POST da Python)
  5. Google fornisce un token crittografico (Non sono abbastanza sicuro di questo passaggio) in risposta alla richiesta di accesso
  6. Si invia la nuova richiesta da a con questo token.
  7. Stackoverflow contatterà google con questo token. Se viene stabilita l'autenticità, verrà restituito un ID di sessione
  8. Le richieste successive a STackOverflow devono avere questo ID di sessione
  9. Nessuna idea di uscire !!

This link indica varie risposte in OpenID e cosa significano. Quindi forse sarà utile quando il tuo codice sarà tuo cliente.

collegamenti dalla pagina wiki OpenID Explained

EDIT: Usando Tamper Data Add on per Firefox, la seguente sequenza di eventi possono essere costruiti.

  1. L'utente invia una richiesta alla pagina di accesso SO. Inserendo l'openID nel campo modulo la pagina risultante invia un reindirizzamento 302 a una pagina google. L'URL di reindirizzamento ha molti parametri OpenID (che sono per il server di google). Uno di questi è return_to = https://stackoverflow.com/users/authenticate/?s=some_value.
  2. L'utente viene presentato con la pagina di accesso di Google. Al login ci sono alcuni 302 che reindirizzano l'utente in giro in google realm.
  3. Finalmente un 302 è ricevuto che reindirizza la pagina utente specificato nel 'return_to' precedenza
  4. di per StackOverflow Durante questa intera serie di operazioni un sacco di biscotto sono state generate, che deve essere conservato correttamente
  5. Sulla accesso al SO pagina (che era 302 su google), il server SO processa la tua richiesta e nell'intestazione della risposta invia un campo "Set-Cookie" per impostare i cookie denominati gauth e usr con un valore insieme a un altro 302 su stackoverflow.com. Questo passaggio completa l'accesso
  6. Il tuo cliente memorizza semplicemente il cookie usr
  7. Sei loggato fintanto che ti ricordi di inviare il cookie usr con qualsiasi richiesta a SO.
  8. Ora è possibile richiedere la propria casella di posta solo per inviare il cookie usr con la richiesta.

Suggerisco di iniziare a codificare il client Python e studiare attentamente le risposte. Nella maggior parte dei casi sarà una serie di 302 con un intervento minimo da parte dell'utente (tranne che per la compilazione del nome utente e della password di Google e per consentire la pagina del sito).

Tuttavia, per semplificare, è sufficiente accedere a SO utilizzando il browser, copiare tutti i valori dei cookie ed effettuare una richiesta utilizzando urllib2 con i valori dei cookie impostati.

Ovviamente in caso di disconnessione dal browser, sarà necessario effettuare nuovamente il login e modificare il valore del cookie nel programma python.

+0

OK, quindi accedo a StackOverflow, che mi reindirizza alla pagina di accesso di es. Google, che, una volta effettuato l'accesso, reindirizza nuovamente a SO. Ma quale URL richiede quindi di accedere a SO? Normalmente, si richiede un URL, che invia un errore 301, e quindi si ottengono le intestazioni di autenticazione, quindi si aggiunge lo schema e il regno all'intestazione della richiesta, insieme a nome utente e password. Ora se richiedo http://stackoverflow.com o http://stackoverflow.com/users/login, non ottengo alcun errore 301 in primo luogo. – Benjamin

+0

Penso che una volta effettuato l'accesso al proprio account google, dovrebbe inviare un reindirizzamento a SO (dopo aver effettuato correttamente l'autenticazione e consentire la pagina del sito). L'URL di reindirizzamento deve avere un parametro che contiene il token crittografico generato da Google per SO. Una volta reindirizzato a tale pagina SO, il server SO elabora il token e, se trovato corretto, imposta un cookie per il resto della sessione. Quindi hai effettuato l'accesso a SO. Puoi richiedere qualsiasi pagina da SO fintanto che invii il cookie con la richiesta e SO ti rispedirà quella pagina. – RedBaron

+0

Utilizzo di firefox Rilevo che la pagina seguente è accessibile dopo aver effettuato il login all'account google. http://stackauth.com/auth/global/write?authToken=SomeValue. Ora se Google reindirizza a questo sito o SO ha nella comunicazione precedente ha detto al browser di reindirizzare a questo link una volta completata l'autenticazione che non conosco. Probabilmente dovresti analizzare la risposta di ogni richiesta e analizzarla in modo che tu possa trovare il link mancante (Mancanza della risposta 301) – RedBaron

0

Ho creato uno script semplice che accede a stackoverflow.com utilizzando i cookie di Mozilla Firefox. Non è completamente automatizzato, perché è necessario effettuare il login manualmente, ma è tutto ciò che sono riuscito a fare.

Scipt è effettivo per le ultime versioni di FF (sto usando 8.0.1), ma è necessario ottenere l'ultima DLL sqlite, perché quella predefinita fornita con python 2.7 non può aprire DB. È possibile ottenere qui: 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

Probabilmente siete a conoscenza dei difetti di questo codice, ma li menziono in modo che tutti lo sappiano. 1. Non portatile (funziona solo su Windows). 2. Non funziona se è presente più di un profilo firefox 3. Non funziona se l'utente non effettua l'accesso entro 60 secondi 4. È necessario attendere anche se l'accesso è avvenuto più rapidamente di 60 secondi 5. Server lato inutile – rds

5

So che questo è vicino alla archeologia, scavare un post che è di due anni, ma ho appena scritto una nuova versione migliorata del codice dalla risposta convalidato, quindi ho pensato che potrebbe essere bello condividerle qui , poiché questa domanda/risposta mi è stata di grande aiuto per implementarla.

Quindi, ecco cosa c'è di diverso:

  • utilizza il nuovo requests libreria che è un miglioramento sopra urllib2;
  • supporta l'autenticazione tramite il provider openid di google e stackexchange.
  • è via più breve e più semplice da leggere, anche se ha meno stampe

Ecco il codice:

#!/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() 

il codice è disponibile anche come github gist

HTH

+0

e ho pubblicato un progetto usando questo per connettersi allo stackoverflow (e alla chat) usando uno strumento da riga di comando: http://github.com/guyzmo/pystackoverflow – zmo

Problemi correlati