如何从使用OpenID的网站请求页面



这个问题以前在这里被问到过。对于提问者和回答者来说,公认的答案可能是显而易见的——但对我来说却不是。我对上述问题进行了评论,以获得更准确的信息,但没有得到回应。我还向meta Q&A寻求帮助,如何从坟墓中带回问题,也没有得到答案。

上面这个问题的答案是:

从客户端的角度来看,OpenID登录与任何其他基于web的登录非常相似。客户端没有一个定义好的协议;它是一个普通的web会话,根据您的OpenID提供商而有所不同。出于这个原因,我怀疑是否存在这样的库。你可能需要自己编写代码。

我知道如何登录到一个网站与Python已经,使用Urllib2模块。但这还不足以让我猜测如何验证OpenID。

我实际上试图得到我的StackOverflow json格式的收件箱,为此我需要登录。

谁能提供一个简短的介绍或链接到一个很好的教程如何做到这一点?

我自己不太了解OpenID,但是你的帖子(和赏金!!)引起了我的兴趣。

这个链接告诉OpenID身份验证序列的确切流程(至少对于v1.0是这样)。新版本是2.0)。从我能看到的,这些步骤应该是类似于

  1. 获取stackoverflow的登录页面,该页面还将提供使用OpenID登录的选项(作为表单字段)。
  2. 你发送你的openID,这实际上是一个uri的形式,而不是用户名/电子邮件(如果它是谷歌的个人资料,它是你的个人资料ID)
  3. Stackoverflow将连接到您的ID提供商(在这种情况下谷歌),并向您发送一个重定向到谷歌登录页面和另一个链接到你应该重定向的地方(让我们说a)
  4. 你可以按惯例登录到google提供的页面(使用Python的POST方法)
  5. Google提供了一个加密令牌(不太确定这一步)来返回您的登录请求
  6. 用这个令牌向a发送新的请求。
  7. Stackoverflow将使用此令牌联系google。如果建立了真实性,它将返回一个会话ID
  8. 以后对STackOverflow的请求应该有这个会话ID
  9. 不知道怎么退出!!

这个链接讲述了OpenID中的各种响应及其含义。因此,当您编写客户端代码时,它可能会派上用场。

来自wiki页面的链接OpenID Explained

编辑:使用Firefox的篡改数据添加,可以构造以下事件序列。

  1. 用户向SO登录页面发送请求。在表单字段中输入openID后,生成的页面发送一个302重定向到一个google页面。重定向URL有很多OpenID参数(用于google服务器)。其中之一是return_to=https://stackoverflow.com/users/authenticate/?s=some_value。
  2. 向用户显示google登录页面。在登录时,有一些302的重定向用户周围的谷歌领域。
  3. 最后收到一个302,它将用户重定向到'return_to'先前中指定的stackoverflow页面
  4. 在整个操作过程中,生成了许多cookie,必须正确存储
  5. 在访问SO页面(这是由谷歌302),SO服务器处理您的请求,并在响应头发送一个字段"set - cookie"设置名为gath和usr的cookie与另一个302到stackoverflow.com的值。此步骤完成登录
  6. 您的客户端只是存储cookie usr
  7. 只要你记得发送Cookie用户的任何请求到SO,你就登录了。
  8. 你现在可以请求你的收件箱,只要记得发送usr cookie的请求。

我建议你开始编写python客户端代码并仔细研究响应。在大多数情况下,它将是一系列302的最小用户干预(除了填写你的谷歌用户名和密码,并允许网站页面)。

然而,为了使它更容易,你可以使用浏览器登录到SO,复制所有的cookie值,并使用设置了cookie值的urllib2发出请求。

当然,如果您退出浏览器,您将不得不再次登录并在python程序中更改cookie值。

我知道这很接近考古学,挖掘一篇两年前的文章,但我刚刚从验证的答案中编写了一个新的增强版本的代码,所以我认为在这里分享它可能很酷,因为这个问题/答案对我实现它有很大的帮助。

不同之处在于:

  • 它使用新的requests库,这是urllib2的增强;
  • 它支持使用google和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

这个答案总结了下面其他人所说的,特别是RedBaron,加上我使用Google帐户访问StackOverflow收件箱的方法。

使用Firefox的篡改数据开发工具并登录到StackOVerflow,可以看到OpenID是这样工作的:

  1. StackOverflow请求来自给定服务(这里是Google)的认证,在发布的数据中定义;
  2. Google Accounts接管并检查已经存在的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)

你需要在任何"login"页面上实现cookie,在Python中你使用cookiejar。例如:

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

我制作了一个简单的脚本,使用Mozilla Firefox cookie登录stackoverflow.com。这不是完全自动化的,因为你需要手动登录,但这是我所能做的。

脚本是实际的最新版本的FF(我使用8.0.1),但你需要得到最新的sqlite dll,因为默认的一个自带python 2.7不能打开DB。你可以在这里下载: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'

最新更新