【问题标题】:How to request pages from website that uses OpenID?如何从使用 OpenID 的网站请求页面?
【发布时间】:2013-06-08 01:04:30
【问题描述】:

This question has been asked here before。接受的答案对提问者和回答者来说可能都是显而易见的——但对我来说不是。我已对上述问题发表评论以获得更多精确度,但没有回应。 I also approached the meta Q&A 寻求有关如何从他们的坟墓中带回问题的帮助,但也没有得到答案。

上述问题的答案是:

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

我已经知道如何log onto a website with Python,使用Urllib2 模块。但这还不足以让我猜测如何对 OpenID 进行身份验证。

我实际上正在尝试获取my StackOverflow inbox in json format,我需要登录。

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

【问题讨论】:

  • PS:我已经标记了这篇文章,以供版主注意故意复制。

标签: python authentication openid urllib2


【解决方案1】:

好吧,我自己对 OpenID 了解不多,但你的帖子(以及赏金!!)让我很感兴趣。

This link 告诉 OpenID 身份验证序列的确切流程(至少 v1.0。新版本是 2.0)。据我所知,这些步骤类似于

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

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

来自维基页面OpenID Explained的链接

编辑:使用 Firefox 的 Tamper Data Add on,可以构建以下事件序列。

  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”来设置名为 gauth 和 usr 的 cookie 以及另一个值302 到 stackoverflow.com。此步骤完成您的登录
  6. 您的客户端只存储 cookie usr
  7. 只要您记得向 SO 发送带有任何请求的 Cookie usr,您就已登录。
  8. 您现在可以请求您的收件箱,只需记住将 usr cookie 与请求一起发送。

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

不过,为了方便起见,您可以使用浏览器登录 SO,复制所有 cookie 值,然后使用设置了 cookie 值的 urllib2 发出请求。

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

【讨论】:

  • 好的,所以我登录到 StackOverflow,它将我重定向到例如登录页面。谷歌,一旦登录,再次将我重定向回 SO。但是您请求哪个 URL 来登录 SO?通常,您请求一个 URL,它会向您发送 301 错误,然后您获得身份验证标头,然后将方案和领域以及用户名和密码添加到请求标头中。现在,如果我请求 stackoverflow.comstackoverflow.com/users/login,我首先不会收到任何 301 错误。
  • 我认为一旦您登录到您的谷歌帐户,它应该将重定向发送回 SO(在成功验证并允许该站点页面后)。重定向 URL 应该有一个参数,其中包含 Google 为 SO 生成的加密令牌。一旦您重定向到该 SO 页面,SO 服务器就会处理该令牌,如果发现正确,则为您的会话的其余部分设置一个 cookie。然后你登录到 SO。您可以向 SO 请求任何页面,只要您将 cookie 与请求一起发送,SO 就会将该页面返回给您。
  • 使用 Firefox 我检测到在我登录 google 帐户后访问了以下页面。 stackauth.com/auth/global/write?authToken=SomeValue。现在,无论是谷歌重定向到这个站点还是 SO 在之前的通信中告诉浏览器在身份验证完成后重定向到这个链接,我不知道。您可能应该解析每个请求的响应并对其进行分析,以便您可以找到丢失的链接(即缺少 301 响应)
  • 感谢您的彻底回复,我会尝试并最终发布工作代码。
  • 如果您想要一个持久的解决方案,那么您可能应该对所有内容(包括重定向)进行编程。否则,为了捷径,你可以储存饼干。就个人而言,我建议走漫长的道路。处理重定向可能很棘手(尤其是在谷歌同时验证你进入 youtube 等的情况下)但你应该在那里寻找解决方案
【解决方案2】:

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

所以,这里有什么不同:

  • 它使用新的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

【讨论】:

【解决方案3】:

这个答案总结了其他人在下面所说的话,尤其是RedBaron,并添加了一个我用来使用 Google 帐户访问 StackOverflow 收件箱的方法。

使用 Firefox 的 Tamper Data 开发者工具并登录 StackOVerflow,可以看到 OpenID 是这样工作的:

  1. StackOverflow 向已发布数据中定义的给定服务(此处为 Google)请求身份验证;
  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)

【讨论】:

  • 呃,为什么是 HTMLParser 而不是 BeautifulSoup 之类的东西?
  • @ThiefMaster:如果您认为 BeautifulSoup 在此方面优于 HTMLParser 有充分的理由,请随时解释和修改答案。
  • 两个简单的函数调用与子类化和 6 个缩进级别
  • @ThiefMaster:好的,我会看看 BeautifulSoup 并尽快编辑答案。
【解决方案4】:

您需要在任何“登录”页面上实现 cookie,在 Python 中您使用 cookiejar。例如:

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

【讨论】:

    【解决方案5】:

    我制作了一个使用 Mozilla Firefox cookie 登录到 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. 不可移植(仅适用于 Windows)。 2. 如果有超过 1 个 firefox 配置文件则不起作用 3. 如果用户在 60 秒内未登录则不起作用 4. 即使登录速度超过 60 秒也必须等待 5. 无用的服务器端
    猜你喜欢
    • 2010-11-23
    • 1970-01-01
    • 1970-01-01
    • 2014-11-25
    • 2019-09-09
    • 2010-09-30
    • 1970-01-01
    • 2016-11-01
    • 2022-01-06
    相关资源
    最近更新 更多